commit
53f454e370
@ -7,6 +7,6 @@ namespace Oqtane.Modules
|
||||
SecurityAccessLevel SecurityAccessLevel { get; } // defines the security access level for this control - defaults to View
|
||||
string Title { get; } // title to display for this control - defaults to module title
|
||||
string Actions { get; } // allows for routing by configuration rather than by convention ( comma delimited ) - defaults to using component file name
|
||||
bool UseAdminContainer { get; } // container for embedding module control - defaults to true
|
||||
bool UseAdminContainer { get; } // container for embedding module control - defaults to true. false will suppress the default modal UI popup behavior and render the component in the page.
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ using Oqtane.UI;
|
||||
|
||||
namespace Oqtane.Modules
|
||||
{
|
||||
public class ModuleBase : ComponentBase, IModuleControl
|
||||
public abstract class ModuleBase : ComponentBase, IModuleControl
|
||||
{
|
||||
private Logger _logger;
|
||||
|
||||
|
@ -4,8 +4,10 @@ using System.Threading.Tasks;
|
||||
using Oqtane.Services;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Providers;
|
||||
@ -19,10 +21,9 @@ namespace Oqtane.Client
|
||||
{
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("app");
|
||||
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
|
||||
|
||||
builder.Services.AddSingleton(
|
||||
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }
|
||||
);
|
||||
builder.Services.AddSingleton(httpClient);
|
||||
builder.Services.AddOptions();
|
||||
|
||||
// register auth services
|
||||
@ -57,14 +58,16 @@ namespace Oqtane.Client
|
||||
builder.Services.AddScoped<ISqlService, SqlService>();
|
||||
builder.Services.AddScoped<ISystemService, SystemService>();
|
||||
|
||||
await LoadClientAssemblies(httpClient);
|
||||
|
||||
// dynamically register module contexts and repository services
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
Type[] implementationtypes = assembly.GetTypes()
|
||||
.Where(item => item.GetInterfaces().Contains(typeof(IService)))
|
||||
.ToArray();
|
||||
foreach (Type implementationtype in implementationtypes)
|
||||
var implementationTypes = assembly.GetTypes()
|
||||
.Where(item => item.GetInterfaces().Contains(typeof(IService)));
|
||||
|
||||
foreach (Type implementationtype in implementationTypes)
|
||||
{
|
||||
Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name));
|
||||
if (servicetype != null)
|
||||
@ -76,9 +79,27 @@ namespace Oqtane.Client
|
||||
builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service
|
||||
}
|
||||
}
|
||||
|
||||
assembly.GetInstances<IClientStartup>()
|
||||
.ToList()
|
||||
.ForEach(x => x.ConfigureServices(builder.Services));
|
||||
}
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
|
||||
private static async Task LoadClientAssemblies(HttpClient http)
|
||||
{
|
||||
var list = await http.GetFromJsonAsync<List<string>>($"/~/api/ModuleDefinition/load");
|
||||
// get list of loaded assemblies on the client ( in the client-side hosting module the browser client has its own app domain )
|
||||
var assemblyList = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
|
||||
foreach (var name in list)
|
||||
{
|
||||
if (assemblyList.Contains(name)) continue;
|
||||
// download assembly from server and load
|
||||
var bytes = await http.GetByteArrayAsync($"/~/api/ModuleDefinition/load/{name}.dll");
|
||||
Assembly.Load(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,6 +170,16 @@ namespace Oqtane.Controllers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// GET api/<controller>/load/assembyname
|
||||
[HttpGet("load")]
|
||||
public List<string> Load()
|
||||
{
|
||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
|
||||
var list = AppDomain.CurrentDomain.GetOqtaneClientAssemblies().Select(a => a.GetName().Name).ToList();
|
||||
var deps = assemblies.SelectMany(a => a.GetReferencedAssemblies()).Distinct();
|
||||
list.AddRange(deps.Where(a=>a.Name.EndsWith(".oqtane",StringComparison.OrdinalIgnoreCase)).Select(a=>a.Name));
|
||||
return list;
|
||||
}
|
||||
|
||||
// POST api/<controller>?moduleid=x
|
||||
[HttpPost]
|
||||
|
26
Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs
Normal file
26
Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Oqtane.Infrastructure;
|
||||
|
||||
namespace Oqtane.Extensions
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
var startUps = AppDomain.CurrentDomain
|
||||
.GetOqtaneAssemblies()
|
||||
.SelectMany(x => x.GetInstances<IServerStartup>());
|
||||
|
||||
foreach (var startup in startUps)
|
||||
{
|
||||
startup.Configure(app, env);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Oqtane.Infrastructure;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
@ -30,6 +31,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mvcBuilder;
|
||||
}
|
||||
|
||||
|
||||
public static IMvcBuilder ConfigureOqtaneMvc(this IMvcBuilder mvcBuilder)
|
||||
{
|
||||
var startUps = AppDomain.CurrentDomain
|
||||
.GetOqtaneAssemblies()
|
||||
.SelectMany(x => x.GetInstances<IServerStartup>());
|
||||
|
||||
foreach (var startup in startUps)
|
||||
{
|
||||
startup.ConfigureMvc(mvcBuilder);
|
||||
}
|
||||
|
||||
return mvcBuilder;
|
||||
}
|
||||
}
|
||||
|
@ -8,21 +8,23 @@ using Microsoft.Extensions.Hosting;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class OqtaneServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddOqtaneParts(this IServiceCollection services)
|
||||
public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime)
|
||||
{
|
||||
LoadAssemblies();
|
||||
services.AddOqtaneServices();
|
||||
services.AddOqtaneServices(runtime);
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddOqtaneServices(this IServiceCollection services)
|
||||
private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
|
||||
{
|
||||
if (services is null)
|
||||
{
|
||||
@ -53,11 +55,24 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddSingleton(hostedServiceType, serviceType);
|
||||
}
|
||||
}
|
||||
|
||||
var startUps = assembly.GetInstances<IServerStartup>();
|
||||
foreach (var startup in startUps)
|
||||
{
|
||||
startup.ConfigureServices(services);
|
||||
}
|
||||
|
||||
if (runtime == Runtime.Server)
|
||||
{
|
||||
assembly.GetInstances<IClientStartup>()
|
||||
.ToList()
|
||||
.ForEach(x => x.ConfigureServices(services));
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
private static void LoadAssemblies()
|
||||
{
|
||||
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
|
||||
|
17
Oqtane.Server/Infrastructure/Interfaces/IServerStartup.cs
Normal file
17
Oqtane.Server/Infrastructure/Interfaces/IServerStartup.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public interface IServerStartup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
void Configure(IApplicationBuilder app, IWebHostEnvironment env);
|
||||
void ConfigureMvc(IMvcBuilder mvcBuilder);
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,13 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
|
||||
namespace Oqtane
|
||||
{
|
||||
@ -26,6 +28,7 @@ namespace Oqtane
|
||||
{
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
private string _webRoot;
|
||||
private Runtime _runtime;
|
||||
|
||||
public Startup(IWebHostEnvironment env)
|
||||
{
|
||||
@ -33,6 +36,9 @@ namespace Oqtane
|
||||
.SetBasePath(env.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
||||
Configuration = builder.Build();
|
||||
|
||||
_runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server;
|
||||
|
||||
_webRoot = env.WebRootPath;
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
|
||||
}
|
||||
@ -187,14 +193,13 @@ namespace Oqtane
|
||||
services.AddTransient<ISqlRepository, SqlRepository>();
|
||||
services.AddTransient<IUpgradeManager, UpgradeManager>();
|
||||
|
||||
// load the external assemblies into the app domain
|
||||
services.AddOqtaneParts();
|
||||
// load the external assemblies into the app domain, install services
|
||||
services.AddOqtaneParts(_runtime);
|
||||
|
||||
services.AddMvc()
|
||||
.AddNewtonsoftJson()
|
||||
.AddOqtaneApplicationParts() // register any Controllers from custom modules
|
||||
.AddNewtonsoftJson();
|
||||
|
||||
|
||||
.ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
|
||||
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
@ -217,14 +222,12 @@ namespace Oqtane
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
@ -237,6 +240,7 @@ namespace Oqtane
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapFallbackToPage("/_Host");
|
||||
});
|
||||
app.ConfigureOqtaneAssemblies(env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
@ -31,7 +32,29 @@ namespace System.Reflection
|
||||
}
|
||||
|
||||
return assembly.GetTypes()
|
||||
.Where(t => t.GetInterfaces().Contains(interfaceType));
|
||||
//.Where(t => t.GetInterfaces().Contains(interfaceType));
|
||||
.Where(x => interfaceType.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetTypes<T>(this Assembly assembly)
|
||||
{
|
||||
return assembly.GetTypes(typeof(T));
|
||||
}
|
||||
|
||||
public static IEnumerable<T> GetInstances<T>(this Assembly assembly) where T : class
|
||||
{
|
||||
if (assembly is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assembly));
|
||||
}
|
||||
var type = typeof(T);
|
||||
var list = assembly.GetTypes()
|
||||
.Where(x => type.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract && !x.IsGenericType);
|
||||
|
||||
foreach (var type1 in list)
|
||||
{
|
||||
if (Activator.CreateInstance(type1) is T instance) yield return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsOqtaneAssembly(this Assembly assembly)
|
||||
@ -48,5 +71,10 @@ namespace System.Reflection
|
||||
{
|
||||
return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly());
|
||||
}
|
||||
public static IEnumerable<Assembly> GetOqtaneClientAssemblies(this AppDomain appDomain)
|
||||
{
|
||||
return appDomain.GetOqtaneAssemblies()
|
||||
.Where(a => a.GetTypes<IClientStartup>().Any());
|
||||
}
|
||||
}
|
||||
}
|
11
Oqtane.Shared/Interfaces/IClientStartup.cs
Normal file
11
Oqtane.Shared/Interfaces/IClientStartup.cs
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
public interface IClientStartup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.2" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
|
Loading…
x
Reference in New Issue
Block a user