implement client and server service implementations in Html/Text module

This commit is contained in:
sbwalker 2024-03-05 08:44:09 -05:00
parent 7d7ea4c34b
commit 74952cf62d
11 changed files with 214 additions and 30 deletions

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
@ -9,7 +8,7 @@ using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services namespace Oqtane.Modules.HtmlText.Services
{ {
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")] [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) {} public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}
@ -30,9 +29,9 @@ namespace Oqtane.Modules.HtmlText.Services
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId)); return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
} }
public async Task AddHtmlTextAsync(Models.HtmlText htmlText) public async Task<Models.HtmlText> 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) public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)

View File

@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Services
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId); Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
Task AddHtmlTextAsync(Models.HtmlText htmltext); Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId); Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
} }

View File

@ -220,6 +220,16 @@ namespace Oqtane.Client
services.AddScoped(serviceType ?? implementationType, implementationType); services.AddScoped(serviceType ?? implementationType, implementationType);
} }
} }
implementationTypes = assembly.GetInterfaces<IClientService>();
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 catch
{ {

View File

@ -292,11 +292,29 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
if (implementationType.AssemblyQualifiedName != null) 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); services.AddScoped(serviceType ?? implementationType, implementationType);
} }
} }
// dynamically register module server scoped services (using conventions)
implementationTypes = assembly.GetInterfaces<IServerService>();
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) // dynamically register module transient services (ie. server DBContext, repository classes)
implementationTypes = assembly.GetInterfaces<ITransientService>(); implementationTypes = assembly.GetInterfaces<ITransientService>();
foreach (var implementationType in implementationTypes) foreach (var implementationType in implementationTypes)

View File

@ -10,6 +10,8 @@ using System.Net;
using Oqtane.Documentation; using Oqtane.Documentation;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Oqtane.Modules.HtmlText.Services;
using System.Threading.Tasks;
namespace Oqtane.Modules.HtmlText.Controllers 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")] [PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextController : ModuleControllerBase 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/<controller>?moduleid=x // GET: api/<controller>?moduleid=x
[HttpGet] [HttpGet]
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public IEnumerable<Models.HtmlText> Get(string moduleId) public async Task<IEnumerable<Models.HtmlText>> Get(string moduleId)
{ {
if (int.TryParse(moduleId, out int ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) if (int.TryParse(moduleId, out int ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
{ {
return _htmlText.GetHtmlTexts(ModuleId); return await _htmlTextService.GetHtmlTextsAsync(ModuleId);
} }
else else
{ {
@ -44,19 +46,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// GET api/<controller>/5 // GET api/<controller>/5
[HttpGet("{moduleId}")] [HttpGet("{moduleId}")]
[Authorize(Policy = PolicyNames.ViewModule)] [Authorize(Policy = PolicyNames.ViewModule)]
public Models.HtmlText Get(int moduleId) public async Task<Models.HtmlText> Get(int moduleId)
{ {
if (IsAuthorizedEntityId(EntityNames.Module, moduleId)) if (IsAuthorizedEntityId(EntityNames.Module, moduleId))
{ {
var htmltexts = _htmlText.GetHtmlTexts(moduleId); return await _htmlTextService.GetHtmlTextAsync(moduleId);
if (htmltexts != null && htmltexts.Any())
{
return htmltexts.OrderByDescending(item => item.CreatedOn).First();
}
else
{
return null;
}
} }
else else
{ {
@ -69,11 +63,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// GET api/<controller>/5/6 // GET api/<controller>/5/6
[HttpGet("{id}/{moduleId}")] [HttpGet("{id}/{moduleId}")]
[Authorize(Policy = PolicyNames.ViewModule)] [Authorize(Policy = PolicyNames.ViewModule)]
public Models.HtmlText Get(int id, int moduleId) public async Task<Models.HtmlText> Get(int id, int moduleId)
{ {
if (IsAuthorizedEntityId(EntityNames.Module, moduleId)) if (IsAuthorizedEntityId(EntityNames.Module, moduleId))
{ {
return _htmlText.GetHtmlText(id); return await _htmlTextService.GetHtmlTextAsync(id, moduleId);
} }
else else
{ {
@ -86,11 +80,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Policy = PolicyNames.EditModule)] [Authorize(Policy = PolicyNames.EditModule)]
public Models.HtmlText Post([FromBody] Models.HtmlText htmlText) public async Task<Models.HtmlText> Post([FromBody] Models.HtmlText htmlText)
{ {
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, htmlText.ModuleId)) 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Html/Text Added {HtmlText}", htmlText);
return htmlText; return htmlText;
} }
@ -105,11 +99,11 @@ namespace Oqtane.Modules.HtmlText.Controllers
// DELETE api/<controller>/5 // DELETE api/<controller>/5
[HttpDelete("{id}/{moduleId}")] [HttpDelete("{id}/{moduleId}")]
[Authorize(Policy = PolicyNames.EditModule)] [Authorize(Policy = PolicyNames.EditModule)]
public void Delete(int id, int moduleId) public async Task Delete(int id, int moduleId)
{ {
if (IsAuthorizedEntityId(EntityNames.Module, 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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Html/Text Deleted {HtmlTextId}", id);
} }
else else

View File

@ -4,6 +4,8 @@ using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using System; using System;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace Oqtane.Modules.HtmlText.Repository namespace Oqtane.Modules.HtmlText.Repository
{ {
@ -51,6 +53,36 @@ namespace Oqtane.Modules.HtmlText.Repository
_db.SaveChanges(); _db.SaveChanges();
} }
public async Task<IEnumerable<Models.HtmlText>> 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<Models.HtmlText> GetHtmlTextAsync(int htmlTextId)
{
return await _db.HtmlText.FindAsync(htmlTextId);
}
public async Task<Models.HtmlText> 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) private void ClearCache(int moduleId)
{ {
_cache.Remove($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}"); _cache.Remove($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}");

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Modules.HtmlText.Models;
namespace Oqtane.Modules.HtmlText.Repository namespace Oqtane.Modules.HtmlText.Repository
{ {
@ -11,5 +11,10 @@ namespace Oqtane.Modules.HtmlText.Repository
Models.HtmlText GetHtmlText(int htmlTextId); Models.HtmlText GetHtmlText(int htmlTextId);
Models.HtmlText AddHtmlText(Models.HtmlText htmlText); Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
void DeleteHtmlText(int htmlTextId); void DeleteHtmlText(int htmlTextId);
Task<IEnumerable<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId);
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText);
Task DeleteHtmlTextAsync(int htmlTextId);
} }
} }

View File

@ -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<List<Models.HtmlText>> 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<Models.HtmlText> 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<Models.HtmlText> 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<Models.HtmlText> 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);
}
}
}
}

View File

@ -92,9 +92,9 @@ namespace Oqtane.Repository
public async Task DeleteSiteAsync(int siteId) public async Task DeleteSiteAsync(int siteId)
{ {
var site = await _db.Site.FindAsync(siteId); var site = _db.Site.Find(siteId);
_db.Site.Remove(site); _db.Site.Remove(site);
_db.SaveChanges(); await _db.SaveChangesAsync();
} }
// synchronous methods // synchronous methods

View File

@ -0,0 +1,9 @@
namespace Oqtane.Modules
{
/// <summary>
/// Empty interface used to decorate client module services for auto registration as scoped
/// </summary>
public interface IClientService
{
}
}

View File

@ -0,0 +1,9 @@
namespace Oqtane.Modules
{
/// <summary>
/// Empty interface used to decorate server module services for auto registration as scoped
/// </summary>
public interface IServerService
{
}
}