using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Oqtane.Enums; using Oqtane.Models; using Oqtane.Shared; namespace Oqtane.Services { public class ServiceBase { private readonly HttpClient _httpClient; private readonly SiteState _siteState; private readonly IHttpClientFactory _factory; protected ServiceBase(HttpClient httpClient, SiteState siteState) { _httpClient = httpClient; _siteState = siteState; } protected ServiceBase(IHttpClientFactory factory, SiteState siteState) { _factory = factory; _siteState = siteState; } public HttpClient GetHttpClient() { if (_factory != null) { var client = _factory.CreateClient("oqtane"); client.BaseAddress = new Uri(_siteState.Alias.Protocol + _siteState.Alias.Name); if (!client.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken)) { client.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken); } return client; } else { if (!_httpClient.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken)) { _httpClient.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken); } return _httpClient; } } // should be used with new constructor public string CreateApiUrl(string serviceName) { if (_siteState != null) { return CreateApiUrl(serviceName, _siteState.Alias, ControllerRoutes.ApiRoute); } else // legacy support (before 2.1.0) { return CreateApiUrl(serviceName, null, ControllerRoutes.Default); } } public string CreateApiUrl(string serviceName, Alias alias) { return CreateApiUrl(serviceName, alias, ControllerRoutes.ApiRoute); } public string CreateApiUrl(string serviceName, Alias alias, string routeTemplate) { string apiurl = "/"; if (routeTemplate == ControllerRoutes.ApiRoute) { if (alias != null && !string.IsNullOrEmpty(alias.Path)) { // include the alias path for multi-tenant context apiurl += alias.Path + "/"; } } else { // legacy support for ControllerRoutes.Default if (alias != null) { // include the alias id for multi-tenant context apiurl += $"{alias.AliasId}/"; } else { // tenant agnostic apiurl += "~/"; } } apiurl += $"api/{serviceName}"; return apiurl; } // add authentityid parameters to url for custom authorization policy public string CreateAuthorizationPolicyUrl(string url, string entityName, int entityId) { return CreateAuthorizationPolicyUrl(url, new Dictionary() { { entityName, entityId } }); } public string CreateAuthorizationPolicyUrl(string url, Dictionary authEntityId) { string qs = ""; foreach (KeyValuePair kvp in authEntityId) { qs += (qs != "") ? "&" : ""; qs += "auth" + kvp.Key.ToLower() + "id=" + kvp.Value.ToString(); } if (url.Contains("?")) { return url + "&" + qs; } else { return url + "?" + qs; } } protected async Task GetAsync(string uri) { var response = await GetHttpClient().GetAsync(uri); await CheckResponse(response, uri); } protected async Task GetStringAsync(string uri) { try { return await GetHttpClient().GetStringAsync(uri); } catch (Exception e) { Console.WriteLine(e); } return default; } protected async Task GetByteArrayAsync(string uri) { try { return await GetHttpClient().GetByteArrayAsync(uri); } catch (Exception e) { Console.WriteLine(e); } return default; } protected async Task GetJsonAsync(string uri) { var response = await GetHttpClient().GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); if (await CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { return await response.Content.ReadFromJsonAsync(); } return default; } protected async Task GetJsonAsync(string uri, T defaultResult) { var response = await GetHttpClient().GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); if (await CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { return await response.Content.ReadFromJsonAsync(); } return defaultResult; } protected async Task PutAsync(string uri) { var response = await GetHttpClient().PutAsync(uri, null); await CheckResponse(response, uri); } protected async Task PutJsonAsync(string uri, T value) { return await PutJsonAsync(uri, value); } protected async Task PutJsonAsync(string uri, TValue value) { var response = await GetHttpClient().PutAsJsonAsync(uri, value); if (await CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { var result = await response.Content.ReadFromJsonAsync(); return result; } return default; } protected async Task PostAsync(string uri) { var response = await GetHttpClient().PostAsync(uri, null); await CheckResponse(response, uri); } protected async Task PostJsonAsync(string uri, T value) { return await PostJsonAsync(uri, value); } protected async Task PostJsonAsync(string uri, TValue value) { var response = await GetHttpClient().PostAsJsonAsync(uri, value); if (await CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { var result = await response.Content.ReadFromJsonAsync(); return result; } return default; } protected async Task DeleteAsync(string uri) { var response = await GetHttpClient().DeleteAsync(uri); await CheckResponse(response, uri); } private async Task CheckResponse(HttpResponseMessage response, string uri) { if (response.IsSuccessStatusCode) { // if response from api call is not from an api url then the route was not mapped correctly if (uri.Contains("/api/") && !response.RequestMessage.RequestUri.AbsolutePath.Contains("/api/")) { await Log(uri, response.RequestMessage.Method.ToString(), response.StatusCode.ToString(), "Request {Uri} Not Mapped To An API Controller Method", uri); return false; } else { return true; } } else { if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound) { await Log(uri, response.RequestMessage.Method.ToString(), response.StatusCode.ToString(), "Request {Uri} Failed With Status {StatusCode} - {ReasonPhrase}", uri, response.StatusCode, response.ReasonPhrase); } return false; } } private static bool ValidateJsonContent(HttpContent content) { var mediaType = content?.Headers.ContentType?.MediaType; return mediaType != null && mediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase); } private async Task Log(string uri, string method, string status, string message, params object[] args) { if (_siteState?.Alias != null && !uri.StartsWith(CreateApiUrl("Log"))) { var log = new Log(); log.SiteId = _siteState.Alias.SiteId; log.PageId = null; log.ModuleId = null; log.UserId = null; log.Url = uri; log.Category = GetType().AssemblyQualifiedName; log.Feature = Utilities.GetTypeNameLastSegment(log.Category, 0); switch (method) { case "GET": log.Function = LogFunction.Read.ToString(); break; case "POST": log.Function = LogFunction.Create.ToString(); break; case "PUT": log.Function = LogFunction.Update.ToString(); break; case "DELETE": log.Function = LogFunction.Delete.ToString(); break; default: log.Function = LogFunction.Other.ToString(); break; } if (status == "500") { log.Level = LogLevel.Error.ToString(); } else { log.Level = LogLevel.Warning.ToString(); } log.Message = message; log.MessageTemplate = ""; log.Properties = JsonSerializer.Serialize(args); await PostJsonAsync(CreateApiUrl("Log"), log); } } //[Obsolete("This constructor is obsolete. Use ServiceBase(HttpClient client, SiteState siteState) : base(http, siteState) {} instead.", false)] // This constructor is obsolete. Use ServiceBase(HttpClient client, SiteState siteState) : base(http, siteState) {} instead. protected ServiceBase(HttpClient client) { _httpClient = client; } [Obsolete("This method is obsolete. Use CreateApiUrl(string serviceName, Alias alias) in conjunction with ControllerRoutes.ApiRoute in Controllers instead.", false)] public string CreateApiUrl(Alias alias, string serviceName) { return CreateApiUrl(serviceName, alias, ControllerRoutes.Default); } [Obsolete("This property of ServiceBase is deprecated. Cross tenant service calls are not supported.", false)] public Alias Alias { get; set; } [Obsolete("This method is obsolete. Use CreateAuthorizationPolicyUrl(string url, string entityName, int entityId) where entityName = EntityNames.Module instead.", false)] public string CreateAuthorizationPolicyUrl(string url, int entityId) { return url + ((url.Contains("?")) ? "&" : "?") + "entityid=" + entityId.ToString(); } } }