diff --git a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
index e4dd0174..f2f0674c 100644
--- a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
+++ b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
@@ -9,7 +8,7 @@ using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
- public class HtmlTextService : ServiceBase, IHtmlTextService, IService
+ public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
{
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}
@@ -30,9 +29,9 @@ namespace Oqtane.Modules.HtmlText.Services
return await GetJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
}
- public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
+ public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
{
- await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
+ return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
}
public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
diff --git a/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs
index 7ca069c6..838d5240 100644
--- a/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs
+++ b/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs
@@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Services
Task GetHtmlTextAsync(int htmlTextId, int moduleId);
- Task AddHtmlTextAsync(Models.HtmlText htmltext);
+ Task AddHtmlTextAsync(Models.HtmlText htmltext);
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
}
diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs
index dad17d44..9f5ed2ff 100644
--- a/Oqtane.Client/Program.cs
+++ b/Oqtane.Client/Program.cs
@@ -220,6 +220,16 @@ namespace Oqtane.Client
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
+
+ implementationTypes = assembly.GetInterfaces();
+ foreach (var implementationType in implementationTypes)
+ {
+ if (implementationType.AssemblyQualifiedName != null)
+ {
+ var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
+ services.AddScoped(serviceType ?? implementationType, implementationType);
+ }
+ }
}
catch
{
diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
index a3324e49..14f97dff 100644
--- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
@@ -292,11 +292,29 @@ namespace Microsoft.Extensions.DependencyInjection
{
if (implementationType.AssemblyQualifiedName != null)
{
- var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
+ var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}")); var serviceName = implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}");
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
+ // dynamically register module server scoped services (using conventions)
+ implementationTypes = assembly.GetInterfaces();
+ foreach (var implementationType in implementationTypes)
+ {
+ if (implementationType.AssemblyQualifiedName != null && implementationType.AssemblyQualifiedName.Contains("Services.Server"))
+ {
+ // module server services reference a common interface which is located in the client assembly
+ var serviceName = implementationType.AssemblyQualifiedName
+ // convert implementation type name to interface name and change Server assembly to Client
+ .Replace(".Services.Server", ".Services.I").Replace(".Server,", ".Client,");
+ var serviceType = Type.GetType(serviceName);
+ if (serviceType != null)
+ {
+ services.AddScoped(serviceType, implementationType);
+ }
+ }
+ }
+
// dynamically register module transient services (ie. server DBContext, repository classes)
implementationTypes = assembly.GetInterfaces();
foreach (var implementationType in implementationTypes)
diff --git a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs
index e6d64241..85fe9f5b 100644
--- a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs
+++ b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs
@@ -10,6 +10,8 @@ using System.Net;
using Oqtane.Documentation;
using System.Collections.Generic;
using System.Linq;
+using Oqtane.Modules.HtmlText.Services;
+using System.Threading.Tasks;
namespace Oqtane.Modules.HtmlText.Controllers
{
@@ -17,21 +19,21 @@ namespace Oqtane.Modules.HtmlText.Controllers
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextController : ModuleControllerBase
{
- private readonly IHtmlTextRepository _htmlText;
+ private readonly IHtmlTextService _htmlTextService;
- public HtmlTextController(IHtmlTextRepository htmlText, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
+ public HtmlTextController(IHtmlTextService htmlTextService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
{
- _htmlText = htmlText;
+ _htmlTextService = htmlTextService;
}
// GET: api/?moduleid=x
[HttpGet]
[Authorize(Roles = RoleNames.Registered)]
- public IEnumerable Get(string moduleId)
+ public async Task> Get(string moduleId)
{
if (int.TryParse(moduleId, out int ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
{
- return _htmlText.GetHtmlTexts(ModuleId);
+ return await _htmlTextService.GetHtmlTextsAsync(ModuleId);
}
else
{
@@ -44,19 +46,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// GET api//5
[HttpGet("{moduleId}")]
[Authorize(Policy = PolicyNames.ViewModule)]
- public Models.HtmlText Get(int moduleId)
+ public async Task Get(int moduleId)
{
if (IsAuthorizedEntityId(EntityNames.Module, moduleId))
{
- var htmltexts = _htmlText.GetHtmlTexts(moduleId);
- if (htmltexts != null && htmltexts.Any())
- {
- return htmltexts.OrderByDescending(item => item.CreatedOn).First();
- }
- else
- {
- return null;
- }
+ return await _htmlTextService.GetHtmlTextAsync(moduleId);
}
else
{
@@ -69,11 +63,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// GET api//5/6
[HttpGet("{id}/{moduleId}")]
[Authorize(Policy = PolicyNames.ViewModule)]
- public Models.HtmlText Get(int id, int moduleId)
+ public async Task Get(int id, int moduleId)
{
if (IsAuthorizedEntityId(EntityNames.Module, moduleId))
{
- return _htmlText.GetHtmlText(id);
+ return await _htmlTextService.GetHtmlTextAsync(id, moduleId);
}
else
{
@@ -86,11 +80,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// POST api/
[HttpPost]
[Authorize(Policy = PolicyNames.EditModule)]
- public Models.HtmlText Post([FromBody] Models.HtmlText htmlText)
+ public async Task Post([FromBody] Models.HtmlText htmlText)
{
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, htmlText.ModuleId))
{
- htmlText = _htmlText.AddHtmlText(htmlText);
+ htmlText = await _htmlTextService.AddHtmlTextAsync(htmlText);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Html/Text Added {HtmlText}", htmlText);
return htmlText;
}
@@ -105,11 +99,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// DELETE api//5
[HttpDelete("{id}/{moduleId}")]
[Authorize(Policy = PolicyNames.EditModule)]
- public void Delete(int id, int moduleId)
+ public async Task Delete(int id, int moduleId)
{
if (IsAuthorizedEntityId(EntityNames.Module, moduleId))
{
- _htmlText.DeleteHtmlText(id);
+ await _htmlTextService.DeleteHtmlTextAsync(id, moduleId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Html/Text Deleted {HtmlTextId}", id);
}
else
diff --git a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs
index 21527009..21e0ffeb 100644
--- a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs
+++ b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure;
using System;
+using Microsoft.EntityFrameworkCore;
+using System.Threading.Tasks;
namespace Oqtane.Modules.HtmlText.Repository
{
@@ -51,6 +53,36 @@ namespace Oqtane.Modules.HtmlText.Repository
_db.SaveChanges();
}
+ public async Task> GetHtmlTextsAsync(int moduleId)
+ {
+ return await _cache.GetOrCreateAsync($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}", async entry =>
+ {
+ entry.SlidingExpiration = TimeSpan.FromMinutes(30);
+ return await _db.HtmlText.Where(item => item.ModuleId == moduleId).ToListAsync();
+ });
+ }
+
+ public async Task GetHtmlTextAsync(int htmlTextId)
+ {
+ return await _db.HtmlText.FindAsync(htmlTextId);
+ }
+
+ public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
+ {
+ _db.HtmlText.Add(htmlText);
+ await _db.SaveChangesAsync();
+ ClearCache(htmlText.ModuleId);
+ return htmlText;
+ }
+
+ public async Task DeleteHtmlTextAsync(int htmlTextId)
+ {
+ Models.HtmlText htmlText = _db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId);
+ _db.HtmlText.Remove(htmlText);
+ ClearCache(htmlText.ModuleId);
+ await _db.SaveChangesAsync();
+ }
+
private void ClearCache(int moduleId)
{
_cache.Remove($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}");
diff --git a/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs b/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs
index 44be5973..8715ccc0 100644
--- a/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs
+++ b/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using Oqtane.Documentation;
-using Oqtane.Modules.HtmlText.Models;
namespace Oqtane.Modules.HtmlText.Repository
{
@@ -11,5 +11,10 @@ namespace Oqtane.Modules.HtmlText.Repository
Models.HtmlText GetHtmlText(int htmlTextId);
Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
void DeleteHtmlText(int htmlTextId);
+
+ Task> GetHtmlTextsAsync(int moduleId);
+ Task GetHtmlTextAsync(int htmlTextId);
+ Task AddHtmlTextAsync(Models.HtmlText htmlText);
+ Task DeleteHtmlTextAsync(int htmlTextId);
}
}
diff --git a/Oqtane.Server/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Server/Modules/HtmlText/Services/HtmlTextService.cs
new file mode 100644
index 00000000..9cbf4ed8
--- /dev/null
+++ b/Oqtane.Server/Modules/HtmlText/Services/HtmlTextService.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Oqtane.Documentation;
+using Oqtane.Enums;
+using Oqtane.Infrastructure;
+using Oqtane.Modules.HtmlText.Repository;
+using Oqtane.Security;
+using Oqtane.Shared;
+
+namespace Oqtane.Modules.HtmlText.Services
+{
+ [PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
+ public class ServerHtmlTextService : IHtmlTextService, IServerService
+ {
+ private readonly IHtmlTextRepository _htmlText;
+ private readonly IUserPermissions _userPermissions;
+ private readonly ITenantManager _tenantManager;
+ private readonly ILogManager _logger;
+ private readonly IHttpContextAccessor _accessor;
+
+ public ServerHtmlTextService(IHtmlTextRepository htmlText, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
+ {
+ _htmlText = htmlText;
+ _userPermissions = userPermissions;
+ _tenantManager = tenantManager;
+ _logger = logger;
+ _accessor = accessor;
+ }
+
+ public async Task> GetHtmlTextsAsync(int moduleId)
+ {
+ if (_accessor.HttpContext.User.IsInRole(RoleNames.Registered))
+ {
+ return (await _htmlText.GetHtmlTextsAsync(moduleId)).ToList();
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Get Attempt {ModuleId}", moduleId);
+ return null;
+ }
+ }
+
+ public async Task GetHtmlTextAsync(int moduleId)
+ {
+ var alias = _tenantManager.GetAlias();
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, alias.SiteId, EntityNames.Module, moduleId, PermissionNames.View))
+ {
+ var htmltexts = await _htmlText.GetHtmlTextsAsync(moduleId);
+ if (htmltexts != null && htmltexts.Any())
+ {
+ return htmltexts.OrderByDescending(item => item.CreatedOn).First();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Get Attempt {ModuleId}", moduleId);
+ return null;
+ }
+ }
+
+ public async Task GetHtmlTextAsync(int htmlTextId, int moduleId)
+ {
+ var alias = _tenantManager.GetAlias();
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, alias.SiteId, EntityNames.Module, moduleId, PermissionNames.View))
+ {
+ return await _htmlText.GetHtmlTextAsync(htmlTextId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Get Attempt {HtmlTextId} {ModuleId}", htmlTextId, moduleId);
+ return null;
+ }
+ }
+
+ public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
+ {
+ var alias = _tenantManager.GetAlias();
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, alias.SiteId, EntityNames.Module, htmlText.ModuleId, PermissionNames.Edit))
+ {
+ return await _htmlText.AddHtmlTextAsync(htmlText);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Add Attempt {HtmlText}", htmlText);
+ return null;
+ }
+ }
+
+ public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
+ {
+ var alias = _tenantManager.GetAlias();
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, alias.SiteId, EntityNames.Module, moduleId, PermissionNames.Edit))
+ {
+ await _htmlText.DeleteHtmlTextAsync(htmlTextId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Delete Attempt {HtmlTextId} {ModuleId}", htmlTextId, moduleId);
+ }
+ }
+ }
+}
diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs
index 8fef197a..a37b70e6 100644
--- a/Oqtane.Server/Repository/SiteRepository.cs
+++ b/Oqtane.Server/Repository/SiteRepository.cs
@@ -92,9 +92,9 @@ namespace Oqtane.Repository
public async Task DeleteSiteAsync(int siteId)
{
- var site = await _db.Site.FindAsync(siteId);
+ var site = _db.Site.Find(siteId);
_db.Site.Remove(site);
- _db.SaveChanges();
+ await _db.SaveChangesAsync();
}
// synchronous methods
diff --git a/Oqtane.Shared/Interfaces/IClientService.cs b/Oqtane.Shared/Interfaces/IClientService.cs
new file mode 100644
index 00000000..ad1bebb7
--- /dev/null
+++ b/Oqtane.Shared/Interfaces/IClientService.cs
@@ -0,0 +1,9 @@
+namespace Oqtane.Modules
+{
+ ///
+ /// Empty interface used to decorate client module services for auto registration as scoped
+ ///
+ public interface IClientService
+ {
+ }
+}
diff --git a/Oqtane.Shared/Interfaces/IServerService.cs b/Oqtane.Shared/Interfaces/IServerService.cs
new file mode 100644
index 00000000..d5d888fd
--- /dev/null
+++ b/Oqtane.Shared/Interfaces/IServerService.cs
@@ -0,0 +1,9 @@
+namespace Oqtane.Modules
+{
+ ///
+ /// Empty interface used to decorate server module services for auto registration as scoped
+ ///
+ public interface IServerService
+ {
+ }
+}