AbstractConnection.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO.Abstractions;
  4. using System.Net.Http;
  5. using System.Net.Http.Headers;
  6. using System.Text.Json;
  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 = new HttpClient();
  50. _client.BaseAddress = new Uri(_uriApiBase, ApiName);
  51. _client.Timeout = _timeout;
  52. _client.DefaultRequestHeaders.Accept.Clear();
  53. _client.DefaultRequestHeaders.Add("User-Agent", "BO.Connector." + ApiName);
  54. // Add an Accept header for JSON format.
  55. _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  56. InitTicket(magicKey);
  57. Login(userName, userPassword);
  58. Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' open for user '{userName}' with timeout setting {_timeout}.");
  59. }
  60. public void Close()
  61. {
  62. _client?.CancelPendingRequests();
  63. _client?.Dispose();
  64. _client = null;
  65. _ticket = string.Empty;
  66. Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' closed.");
  67. }
  68. public bool Ping(out TimeSpan duration, out string status)
  69. {
  70. CheckIsOpen();
  71. var start = DateTime.Now;
  72. var ping = Task.Run(() =>CallRequestAsync<ResultValueDto<PingDto>>($"{_ticket}/ping", RestCallTypeEnum.Get, null, null, true));
  73. ping.Wait();
  74. duration = DateTime.Now - start;
  75. if (ping.Result == null)
  76. {
  77. status = string.Empty;
  78. return false;
  79. }
  80. status = ping.Result.Value.Status;
  81. return ping.Result.IsSuccess;
  82. }
  83. #endregion
  84. #region *** Virtuals & Overrides ***
  85. protected override void OnDisposing()
  86. {
  87. _client?.Dispose();
  88. }
  89. #endregion
  90. #region *** Private Operations ***
  91. protected void CheckIsOpen()
  92. {
  93. if (!IsOpen)
  94. throw new InvalidOperationException(
  95. "Connector is not open to call specific operation. Call Open() first.");
  96. }
  97. protected void Log(LogSeverityEnum type, string message, Exception e = null)
  98. {
  99. _log?.Log(type, message, e);
  100. }
  101. protected void InitTicket(string magicKey)
  102. {
  103. var result = Task.Run(() => CallRequestAsync<ResultValueDto<AuthDto>>($"{magicKey}/auth", RestCallTypeEnum.Get));;
  104. result.Wait();
  105. if (!result.Result.IsSuccess)
  106. throw result.Result.ToAggregateException();
  107. _ticket = result.Result.Value.Ticket;
  108. }
  109. protected void Login(string userName, string userPassword)
  110. {
  111. var headerAtts = new Dictionary<string, string>();
  112. headerAtts.Add("userName",userName);
  113. headerAtts.Add("userPassword",userPassword);
  114. var result = Task.Run(() => CallRequestAsync<ResultValueDto<UserRDto>>($"{_ticket}/login", RestCallTypeEnum.Post, new ResultPlain(), headerAtts));
  115. result.Wait();
  116. if (!result.Result.IsSuccess)
  117. throw result.Result.ToAggregateException();
  118. Log(LogSeverityEnum.Info,$"User '{result.Result.Value.Name}' is logged in.");
  119. }
  120. protected IDictionary<string, string> GetPagingHeaderAttributes(PagingDto paging)
  121. {
  122. if (paging == null || paging.IsDisabled)
  123. return null;//paging = new PagingDto();
  124. var result = new Dictionary<string, string>
  125. {
  126. { "page", paging.Page.ToString() },
  127. { "pageSize", paging.PageSize.ToString() }
  128. };
  129. return result;
  130. }
  131. protected async Task<TResult> CallRequestAsync<TResult>(string relativeUrlApiCall,RestCallTypeEnum method, IDto data = null,IDictionary<string,string> headerAttributes = null, bool silent = false) where TResult : ResultDto
  132. {
  133. if (data == null)
  134. if(method != RestCallTypeEnum.Get && method != RestCallTypeEnum.GetStream && method != RestCallTypeEnum.Delete)
  135. throw new ArgumentNullException(nameof(data), $"For REST API method '{method}' data must be specified!");
  136. Log(LogSeverityEnum.Trace, $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
  137. var start = DateTime.Now;
  138. HttpResponseMessage resp = null;
  139. switch (method)
  140. {
  141. case RestCallTypeEnum.Get:
  142. if (headerAttributes != null)
  143. {
  144. foreach (var key in headerAttributes.Keys)
  145. {
  146. _client.DefaultRequestHeaders.Remove(key);
  147. _client.DefaultRequestHeaders.Add(key,headerAttributes[key]);
  148. }
  149. }
  150. resp = await _client.GetAsync(relativeUrlApiCall);
  151. break;
  152. case RestCallTypeEnum.Post:
  153. var req = new StringContent(_binder.SaveToString(data));
  154. if (headerAttributes!=null)
  155. foreach (var key in headerAttributes.Keys)
  156. req.Headers.Add(key,headerAttributes[key]);
  157. req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  158. resp = await _client.PostAsync(relativeUrlApiCall, req);
  159. break;
  160. case RestCallTypeEnum.Put:
  161. req = new StringContent(_binder.SaveToString(data));
  162. if (headerAttributes!=null)
  163. foreach (var key in headerAttributes.Keys)
  164. req.Headers.Add(key,headerAttributes[key]);
  165. req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  166. resp = await _client.PutAsync(relativeUrlApiCall, req);
  167. break;
  168. case RestCallTypeEnum.Delete:
  169. resp = await _client.DeleteAsync(relativeUrlApiCall);
  170. break;
  171. default:
  172. throw new NotSupportedException($"{method} REST API operation is not implemented.");
  173. }
  174. // may remove paging headers after call
  175. /*
  176. if (headerAttributes != null)
  177. foreach (var key in headerAttributes.Keys)
  178. _client.DefaultRequestHeaders.Remove(key);
  179. */
  180. Log(LogSeverityEnum.Trace, $"REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} done @ {(DateTime.Now - start).ToReadableString()}");
  181. if (!resp.IsSuccessStatusCode)
  182. {
  183. var message = $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} ends with result code '{resp.StatusCode}'";
  184. Log(LogSeverityEnum.Warn, message);
  185. throw new Exception(message);
  186. }
  187. var content = await resp.Content.ReadAsStringAsync();
  188. if (_isDumpContentEnabled)
  189. Log(LogSeverityEnum.Trace, content);
  190. var result = (TResult)_binder.LoadFromString(content, typeof(TResult));
  191. if (result.IsSuccess) return result;
  192. if (silent) return result;
  193. // handle errors
  194. var excps = new List<Exception>();
  195. foreach (var err in result.Errors)
  196. {
  197. var exc = new Exception($"[{err.Code}] {err.Message}");
  198. exc.Data.Add("Code", err.Code);
  199. excps.Add(exc);
  200. }
  201. throw new AggregateException(excps);
  202. }
  203. #endregion
  204. }
  205. }