Merge pull request #1 from oqtane/dev

merge
This commit is contained in:
Leigh Pointer 2021-06-16 09:19:09 +02:00 committed by GitHub
commit 0f208f3c30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 617 additions and 372 deletions

View File

@ -1,3 +1,5 @@
using Microsoft.Extensions.Localization;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("Oqtane")]
[assembly: InternalsVisibleTo("Oqtane.Server")]

View File

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Components.Authorization;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
{
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
{
services.AddAuthorizationCore();
services.AddScoped<IdentityAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
return services;
}
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
{
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
services.AddScoped<IThemeService, ThemeService>();
services.AddScoped<IAliasService, AliasService>();
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ISiteService, SiteService>();
services.AddScoped<IPageService, PageService>();
services.AddScoped<IModuleService, ModuleService>();
services.AddScoped<IPageModuleService, PageModuleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProfileService, ProfileService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IPackageService, PackageService>();
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<ISyncService, SyncService>();
return services;
}
}
}

View File

@ -2,7 +2,7 @@
@inherits ModuleBase
@inject IPageService PageService
@inject IUserService UserService
@inject IStringLocalizer<SharedResources> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row">
@foreach (var p in _pages)
@ -12,7 +12,7 @@
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center">
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@Localizer[p.Name]
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink>
</div>
}

View File

@ -33,4 +33,8 @@
<ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
</Project>

View File

@ -8,13 +8,11 @@ using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Modules;
using Oqtane.Providers;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
@ -27,7 +25,8 @@ namespace Oqtane.Client
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton(httpClient);
builder.Services.AddOptions();
@ -36,40 +35,10 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<IdentityAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
builder.Services.AddOqtaneAuthorization();
// register scoped core services
builder.Services.AddScoped<SiteState>();
builder.Services.AddScoped<IInstallationService, InstallationService>();
builder.Services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
builder.Services.AddScoped<IThemeService, ThemeService>();
builder.Services.AddScoped<IAliasService, AliasService>();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<ISiteService, SiteService>();
builder.Services.AddScoped<IPageService, PageService>();
builder.Services.AddScoped<IModuleService, ModuleService>();
builder.Services.AddScoped<IPageModuleService, PageModuleService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IProfileService, ProfileService>();
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IUserRoleService, UserRoleService>();
builder.Services.AddScoped<ISettingService, SettingService>();
builder.Services.AddScoped<IPackageService, PackageService>();
builder.Services.AddScoped<ILogService, LogService>();
builder.Services.AddScoped<IJobService, JobService>();
builder.Services.AddScoped<IJobLogService, JobLogService>();
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IFolderService, FolderService>();
builder.Services.AddScoped<IFileService, FileService>();
builder.Services.AddScoped<ISiteTemplateService, SiteTemplateService>();
builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>();
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
builder.Services.AddScoped<ILanguageService, LanguageService>();
builder.Services.AddScoped<IDatabaseService, DatabaseService>();
builder.Services.AddScoped<ISyncService, SyncService>();
builder.Services.AddOqtaneScopedServices();
await LoadClientAssemblies(httpClient);
@ -77,38 +46,15 @@ namespace Oqtane.Client
foreach (var assembly in assemblies)
{
// dynamically register module services
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
builder.Services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
RegisterModuleServices(assembly, builder.Services);
// register client startup services
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(builder.Services);
}
RegisterClientStartups(assembly, builder.Services);
}
var host = builder.Build();
var jsRuntime = host.Services.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie).UICultures[0].Value;
var localizationService = host.Services.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
await SetCultureFromLocalizationCookie(host.Services);
ServiceActivator.Configure(host.Services);
@ -164,6 +110,45 @@ namespace Oqtane.Client
}
}
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
var implementationTypes = assembly.GetInterfaces<IService>();
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);
}
}
}
private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
{
var startUps = assembly.GetInstances<IClientStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
}
}
private static async Task SetCultureFromLocalizationCookie(IServiceProvider serviceProvider)
{
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie).UICultures[0].Value;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync();
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
}
private static void SetCulture(string culture)
{
var cultureInfo = CultureInfo.GetCultureInfo(culture);

View File

@ -1,7 +0,0 @@
namespace Oqtane
{
public class SharedResources
{
}
}

View File

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="True" xml:space="preserve">
<value>True</value>
</data>
<data name="False" xml:space="preserve">
<value>False</value>
</data>
<data name="Yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="No" xml:space="preserve">
<value>No</value>
</data>
<data name="Save" xml:space="preserve">
<value>Save</value>
</data>
<data name="Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Admin Dashboard" xml:space="preserve">
<value>Admin Dashboard</value>
</data>
<data name="User Login" xml:space="preserve">
<value>User Login</value>
</data>
<data name="User Registration" xml:space="preserve">
<value>User Registration</value>
</data>
<data name="Password Reset" xml:space="preserve">
<value>Password Reset</value>
</data>
<data name="User Profile" xml:space="preserve">
<value>User Profile</value>
</data>
<data name="Site Settings" xml:space="preserve">
<value>Site Settings</value>
</data>
<data name="Page Management" xml:space="preserve">
<value>Page Management</value>
</data>
<data name="User Management" xml:space="preserve">
<value>User Management</value>
</data>
<data name="Profile Management" xml:space="preserve">
<value>Profile Management</value>
</data>
<data name="Role Management" xml:space="preserve">
<value>Role Management</value>
</data>
<data name="File Management" xml:space="preserve">
<value>File Management</value>
</data>
<data name="Recycle Bin" xml:space="preserve">
<value>Recycle Bin</value>
</data>
<data name="Event Log" xml:space="preserve">
<value>Event Log</value>
</data>
<data name="Site Management" xml:space="preserve">
<value>Site Management</value>
</data>
<data name="Module Management" xml:space="preserve">
<value>Module Management</value>
</data>
<data name="Theme Management" xml:space="preserve">
<value>Theme Management</value>
</data>
<data name="Language Management" xml:space="preserve">
<value>Language Management</value>
</data>
<data name="Scheduled Jobs" xml:space="preserve">
<value>Scheduled Jobs</value>
</data>
<data name="Sql Management" xml:space="preserve">
<value>Sql Management</value>
</data>
<data name="System Info" xml:space="preserve">
<value>System Info</value>
</data>
<data name="System Update" xml:space="preserve">
<value>System Update</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
using Oqtane.Models;
using Oqtane.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -9,11 +9,5 @@ namespace Oqtane.Services
Task<List<JobLog>> GetJobLogsAsync();
Task<JobLog> GetJobLogAsync(int jobLogId);
Task<JobLog> AddJobLogAsync(JobLog jobLog);
Task<JobLog> UpdateJobLogAsync(JobLog jobLog);
Task DeleteJobLogAsync(int jobLogId);
}
}

View File

@ -21,26 +21,5 @@ namespace Oqtane.Services
/// <param name="tenantId">ID-reference of the <see cref="Tenant"/></param>
/// <returns></returns>
Task<Tenant> GetTenantAsync(int tenantId);
/// <summary>
/// Add / save another <see cref="Tenant"/> to the database
/// </summary>
/// <param name="tenant">A <see cref="Tenant"/> object containing the configuration</param>
/// <returns></returns>
Task<Tenant> AddTenantAsync(Tenant tenant);
/// <summary>
/// Update the <see cref="Tenant"/> information in the database.
/// </summary>
/// <param name="tenant"></param>
/// <returns></returns>
Task<Tenant> UpdateTenantAsync(Tenant tenant);
/// <summary>
/// Delete / remove a <see cref="Tenant"/>
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
Task DeleteTenantAsync(int tenantId);
}
}

View File

@ -30,19 +30,5 @@ namespace Oqtane.Services
{
return await GetJsonAsync<JobLog>($"{Apiurl}/{jobLogId}");
}
public async Task<JobLog> AddJobLogAsync(JobLog joblog)
{
return await PostJsonAsync<JobLog>(Apiurl, joblog);
}
public async Task<JobLog> UpdateJobLogAsync(JobLog joblog)
{
return await PutJsonAsync<JobLog>($"{Apiurl}/{joblog.JobLogId}", joblog);
}
public async Task DeleteJobLogAsync(int jobLogId)
{
await DeleteAsync($"{Apiurl}/{jobLogId}");
}
}
}

View File

@ -30,20 +30,5 @@ namespace Oqtane.Services
{
return await GetJsonAsync<Tenant>($"{Apiurl}/{tenantId}");
}
public async Task<Tenant> AddTenantAsync(Tenant tenant)
{
return await PostJsonAsync<Tenant>(Apiurl, tenant);
}
public async Task<Tenant> UpdateTenantAsync(Tenant tenant)
{
return await PutJsonAsync<Tenant>($"{Apiurl}/{tenant.TenantId}", tenant);
}
public async Task DeleteTenantAsync(int tenantId)
{
await DeleteAsync($"{Apiurl}/{tenantId}");
}
}
}

View File

@ -0,0 +1,14 @@
namespace Oqtane.Client
{
/// <summary>
/// Dummy class used to collect shared resource strings for this application
/// </summary>
/// <remarks>
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
/// The class must reside at the project root.
/// </remarks>
public class SharedResources
{
}
}

View File

@ -10,6 +10,7 @@
@using Microsoft.Extensions.Localization
@using Microsoft.JSInterop
@using Oqtane.Client
@using Oqtane.Models
@using Oqtane.Modules
@using Oqtane.Modules.Controls
@ -22,3 +23,4 @@
@using Oqtane.UI
@using Oqtane.Enums
@using Oqtane.Installer
@using Oqtane.Interfaces

View File

@ -72,7 +72,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)]
public Alias Put(int id, [FromBody] Alias alias)
{
if (ModelState.IsValid)
if (ModelState.IsValid && _aliases.GetAlias(alias.AliasId, false) != null)
{
alias = _aliases.UpdateAlias(alias);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias);
@ -91,8 +91,17 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
_aliases.DeleteAlias(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
var alias = _aliases.GetAlias(id);
if (alias != null)
{
_aliases.DeleteAlias(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Alias Delete Attempt {AliasId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Net;
namespace Oqtane.Controllers
{
@ -52,6 +53,12 @@ namespace Oqtane.Controllers
job = _jobs.AddJob(job);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Job Added {Job}", job);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Post Attempt {Alias}", job);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
job = null;
}
return job;
}
@ -60,11 +67,17 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)]
public Job Put(int id, [FromBody] Job job)
{
if (ModelState.IsValid)
if (ModelState.IsValid && _jobs.GetJob(job.JobId, false) != null)
{
job = _jobs.UpdateJob(job);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Job Updated {Job}", job);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Put Attempt {Alias}", job);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
job = null;
}
return job;
}
@ -73,8 +86,17 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
_jobs.DeleteJob(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Job Deleted {JobId}", id);
var job = _jobs.GetJob(id);
if (job != null)
{
_jobs.DeleteJob(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Job Deleted {JobId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Delete Attempt {JobId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
// GET api/<controller>/start
@ -83,12 +105,17 @@ namespace Oqtane.Controllers
public void Start(int id)
{
Job job = _jobs.GetJob(id);
Type jobtype = Type.GetType(job.JobType);
if (jobtype != null)
if (job != null)
{
Type jobtype = Type.GetType(job.JobType);
var jobobject = ActivatorUtilities.CreateInstance(_serviceProvider, jobtype);
((IHostedService)jobobject).StartAsync(new System.Threading.CancellationToken());
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Start Attempt {JobId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
// GET api/<controller>/stop
@ -97,12 +124,17 @@ namespace Oqtane.Controllers
public void Stop(int id)
{
Job job = _jobs.GetJob(id);
Type jobtype = Type.GetType(job.JobType);
if (jobtype != null)
if (job != null)
{
Type jobtype = Type.GetType(job.JobType);
var jobobject = ActivatorUtilities.CreateInstance(_serviceProvider, jobtype);
((IHostedService)jobobject).StopAsync(new System.Threading.CancellationToken());
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Stop Attempt {JobId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@ -1,10 +1,8 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Enums;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Infrastructure;
using Oqtane.Repository;
namespace Oqtane.Controllers
@ -13,12 +11,10 @@ namespace Oqtane.Controllers
public class JobLogController : Controller
{
private readonly IJobLogRepository _jobLogs;
private readonly ILogManager _logger;
public JobLogController(IJobLogRepository jobLogs, ILogManager logger)
public JobLogController(IJobLogRepository jobLogs)
{
_jobLogs = jobLogs;
_logger = logger;
}
// GET: api/<controller>
@ -36,40 +32,5 @@ namespace Oqtane.Controllers
{
return _jobLogs.GetJobLog(id);
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public JobLog Post([FromBody] JobLog jobLog)
{
if (ModelState.IsValid)
{
jobLog = _jobLogs.AddJobLog(jobLog);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Job Log Added {JobLog}", jobLog);
}
return jobLog;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public JobLog Put(int id, [FromBody] JobLog jobLog)
{
if (ModelState.IsValid)
{
jobLog = _jobLogs.UpdateJobLog(jobLog);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Job Log Updated {JobLog}", jobLog);
}
return jobLog;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
_jobLogs.DeleteJobLog(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Job Log Deleted {JobLogId}", id);
}
}
}

View File

@ -36,40 +36,5 @@ namespace Oqtane.Controllers
{
return _tenants.GetTenant(id);
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public Tenant Post([FromBody] Tenant tenant)
{
if (ModelState.IsValid)
{
tenant = _tenants.AddTenant(tenant);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Tenant Added {TenantId}", tenant.TenantId);
}
return tenant;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public Tenant Put(int id, [FromBody] Tenant tenant)
{
if (ModelState.IsValid)
{
tenant = _tenants.UpdateTenant(tenant);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Tenant Updated {TenantId}", tenant.TenantId);
}
return tenant;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
_tenants.DeleteTenant(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Tenant Deleted {TenantId}", id);
}
}
}

View File

@ -11,6 +11,7 @@ using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Text.Json;
using System.Net;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -84,6 +85,11 @@ namespace Oqtane.Controllers
_themes.DeleteTheme(theme.ThemeName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Delete Attempt {Themename}", themename);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
// GET: api/<controller>/templates
@ -141,6 +147,12 @@ namespace Oqtane.Controllers
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, theme);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Theme Created {Theme}", theme);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Post Attempt {Theme}", theme);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
theme = null;
}
return theme;
}

View File

@ -1,16 +1,23 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Oqtane.Infrastructure;
using Oqtane.Interfaces;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
using Oqtane.Shared;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
@ -24,6 +31,161 @@ namespace Microsoft.Extensions.DependencyInjection
return services;
}
public static IServiceCollection AddOqtaneDbContext(this IServiceCollection services)
{
services.AddDbContext<MasterDBContext>(options => { });
services.AddDbContext<TenantDBContext>(options => { });
return services;
}
public static IServiceCollection AddOqtaneAuthorizationPolicies(this IServiceCollection services)
{
services.AddAuthorizationCore(options =>
{
options.AddPolicy(PolicyNames.ViewPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ListFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Browse)));
});
return services;
}
internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services)
{
services.AddSingleton<IInstallationManager, InstallationManager>();
services.AddSingleton<ISyncManager, SyncManager>();
services.AddSingleton<IDatabaseManager, DatabaseManager>();
services.AddSingleton<IConfigManager, ConfigManager>();
return services;
}
internal static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services)
{
services.AddTransient<ITenantManager, TenantManager>();
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
services.AddTransient<IThemeRepository, ThemeRepository>();
services.AddTransient<IUserPermissions, UserPermissions>();
services.AddTransient<ITenantResolver, TenantResolver>();
services.AddTransient<IAliasRepository, AliasRepository>();
services.AddTransient<ITenantRepository, TenantRepository>();
services.AddTransient<ISiteRepository, SiteRepository>();
services.AddTransient<IPageRepository, PageRepository>();
services.AddTransient<IModuleRepository, ModuleRepository>();
services.AddTransient<IPageModuleRepository, PageModuleRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<IProfileRepository, ProfileRepository>();
services.AddTransient<IRoleRepository, RoleRepository>();
services.AddTransient<IUserRoleRepository, UserRoleRepository>();
services.AddTransient<IPermissionRepository, PermissionRepository>();
services.AddTransient<ISettingRepository, SettingRepository>();
services.AddTransient<ILogRepository, LogRepository>();
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<ILocalizationManager, LocalizationManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<IFolderRepository, FolderRepository>();
services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<ISiteTemplateRepository, SiteTemplateRepository>();
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
// obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>();
return services;
}
public static IServiceCollection ConfigureOqtaneCookieOptions(this IServiceCollection services)
{
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync;
});
return services;
}
public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services)
{
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
return services;
}
internal static IServiceCollection TryAddHttpClientWithAuthenticationCookie(this IServiceCollection services)
{
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
services.AddScoped(s =>
{
// creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var navigationManager = s.GetRequiredService<NavigationManager>();
var client = new HttpClient(new HttpClientHandler { UseCookies = false });
client.BaseAddress = new Uri(navigationManager.Uri);
// set the cookies to allow HttpClient API calls to be authenticated
var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
}
return client;
});
}
return services;
}
internal static IServiceCollection TryAddSwagger(this IServiceCollection services, bool useSwagger)
{
if (useSwagger)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Oqtane", Version = "v1" });
});
}
return services;
}
private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
{
if (services is null)

View File

@ -45,15 +45,28 @@ namespace Oqtane.Repository
public Alias GetAlias(int aliasId)
{
return _db.Alias.Find(aliasId);
return GetAlias(aliasId, true);
}
public Alias GetAlias(string name)
public Alias GetAlias(int aliasId, bool tracking)
{
if (tracking)
{
return _db.Alias.Find(aliasId);
}
else
{
return _db.Alias.AsNoTracking().FirstOrDefault(item => item.AliasId == aliasId);
}
}
// lookup alias based on url - note that alias values are hierarchical
public Alias GetAlias(string url)
{
Alias alias = null;
List<Alias> aliases = GetAliases().ToList();
var segments = name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var segments = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// iterate segments to find keywords
int start = segments.Length;

View File

@ -9,7 +9,8 @@ namespace Oqtane.Repository
Alias AddAlias(Alias alias);
Alias UpdateAlias(Alias alias);
Alias GetAlias(int aliasId);
Alias GetAlias(string name);
Alias GetAlias(int aliasId, bool tracking);
Alias GetAlias(string url);
void DeleteAlias(int aliasId);
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
@ -9,6 +9,7 @@ namespace Oqtane.Repository
Job AddJob(Job job);
Job UpdateJob(Job job);
Job GetJob(int jobId);
Job GetJob(int jobId, bool tracking);
void DeleteJob(int jobId);
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@ -48,6 +48,19 @@ namespace Oqtane.Repository
return _db.Job.Find(jobId);
}
public Job GetJob(int jobId, bool tracking)
{
if (tracking)
{
return _db.Job.Find(jobId);
}
else
{
return _db.Job.AsNoTracking().FirstOrDefault(item => item.JobId == jobId);
}
}
public void DeleteJob(int jobId)
{
Job job = _db.Job.Find(jobId);

View File

@ -1,36 +1,29 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
using Oqtane.Shared;
namespace Oqtane
{
public class Startup
{
private Runtime _runtime;
private bool _useSwagger;
private IWebHostEnvironment _env;
private string[] _supportedCultures;
private readonly Runtime _runtime;
private readonly bool _useSwagger;
private readonly IWebHostEnvironment _env;
private readonly string[] _supportedCultures;
public IConfigurationRoot Configuration { get; }
@ -61,77 +54,24 @@ namespace Oqtane
services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection));
services.AddServerSideBlazor().AddCircuitOptions(options =>
{
if (_env.IsDevelopment())
services.AddServerSideBlazor()
.AddCircuitOptions(options =>
{
options.DetailedErrors = true;
}
});
if (_env.IsDevelopment())
{
options.DetailedErrors = true;
}
});
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
services.AddScoped(s =>
{
// creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var navigationManager = s.GetRequiredService<NavigationManager>();
var client = new HttpClient(new HttpClientHandler { UseCookies = false });
client.BaseAddress = new Uri(navigationManager.Uri);
// set the cookies to allow HttpClient API calls to be authenticated
var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
}
return client;
});
}
services.TryAddHttpClientWithAuthenticationCookie();
// register custom authorization policies
services.AddAuthorizationCore(options =>
{
options.AddPolicy(PolicyNames.ViewPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ListFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Browse)));
});
services.AddOqtaneAuthorizationPolicies();
// register scoped core services
services.AddScoped<SiteState>();
services.AddScoped<IAuthorizationHandler, PermissionHandler>();
services.AddScoped<IInstallationService, InstallationService>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
services.AddScoped<IThemeService, ThemeService>();
services.AddScoped<IAliasService, AliasService>();
services.AddScoped<ITenantService, TenantService>();
services.AddScoped<ISiteService, SiteService>();
services.AddScoped<IPageService, PageService>();
services.AddScoped<IModuleService, ModuleService>();
services.AddScoped<IPageModuleService, PageModuleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IProfileService, ProfileService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserRoleService, UserRoleService>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IPackageService, PackageService>();
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<ISyncService, SyncService>();
services.AddScoped<IAuthorizationHandler, PermissionHandler>()
.AddOqtaneScopedServices();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -141,44 +81,12 @@ namespace Oqtane
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<ClaimsPrincipalFactory<IdentityUser>>(); // role claims
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
services.ConfigureOqtaneIdentityOptions();
services.AddAuthentication(Constants.AuthenticationScheme)
.AddCookie(Constants.AuthenticationScheme);
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
};
options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync;
});
services.ConfigureOqtaneCookieOptions();
services.AddAntiforgery(options =>
{
@ -190,51 +98,18 @@ namespace Oqtane
});
// register singleton scoped core services
services.AddSingleton(Configuration);
services.AddSingleton<IInstallationManager, InstallationManager>();
services.AddSingleton<ISyncManager, SyncManager>();
services.AddSingleton<IDatabaseManager, DatabaseManager>();
services.AddSingleton<IConfigManager, ConfigManager>();
services.AddSingleton(Configuration)
.AddOqtaneSingletonServices();
// install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain )
InstallationManager.InstallPackages(_env.WebRootPath, _env.ContentRootPath);
// register transient scoped core services
services.AddTransient<ITenantManager, TenantManager>();
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
services.AddTransient<IThemeRepository, ThemeRepository>();
services.AddTransient<IUserPermissions, UserPermissions>();
services.AddTransient<IAliasRepository, AliasRepository>();
services.AddTransient<ITenantRepository, TenantRepository>();
services.AddTransient<ISiteRepository, SiteRepository>();
services.AddTransient<IPageRepository, PageRepository>();
services.AddTransient<IModuleRepository, ModuleRepository>();
services.AddTransient<IPageModuleRepository, PageModuleRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<IProfileRepository, ProfileRepository>();
services.AddTransient<IRoleRepository, RoleRepository>();
services.AddTransient<IUserRoleRepository, UserRoleRepository>();
services.AddTransient<IPermissionRepository, PermissionRepository>();
services.AddTransient<ISettingRepository, SettingRepository>();
services.AddTransient<ILogRepository, LogRepository>();
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<ILocalizationManager, LocalizationManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<IFolderRepository, FolderRepository>();
services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<ISiteTemplateRepository, SiteTemplateRepository>();
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
// obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>();
services.AddOqtaneTransientServices();
// load the external assemblies into the app domain, install services
services.AddOqtane(_runtime, _supportedCultures);
services.AddDbContext<MasterDBContext>(options => { });
services.AddDbContext<TenantDBContext>(options => { });
services.AddOqtaneDbContext();
services.AddMvc()
@ -242,10 +117,7 @@ namespace Oqtane
.AddOqtaneApplicationParts() // register any Controllers from custom modules
.ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
if (_useSwagger)
{
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Oqtane", Version = "v1"}); });
}
services.TryAddSwagger(_useSwagger);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -65,7 +65,6 @@ V.2.1.0 ( Jun 4, 2021 )
- [x] Centralize package installation and uninstall
- [x] Enable pre-rendering support for Blazor Server
- [x] Allow run-time installation of Language packages
- [x] Add support for Shared localization resources
V.2.0.2 ( Apr 19, 2021 )
- [x] Assorted fixes and user experience improvements