mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-19 15:51:39 +00:00
Update
This commit is contained in:
1 parent
a5dd48a5ed
commit
f72a8aa59f
11 files changed
+323
-208
No files matched your search
Binary file not shown.
Binary file not shown.
@@ -1,142 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json.Serialization;
|
||||
using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Net.Manager;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Empostor.Plugins.Monitor;
|
||||
|
||||
[ApiController]
|
||||
public sealed class MonitorController : ControllerBase
|
||||
{
|
||||
private static readonly DateTime StartTime = Process.GetCurrentProcess().StartTime.ToUniversalTime();
|
||||
|
||||
private readonly IGameManager _gameManager;
|
||||
private readonly IClientManager _clientManager;
|
||||
|
||||
public MonitorController(IGameManager gameManager, IClientManager clientManager)
|
||||
{
|
||||
_gameManager = gameManager;
|
||||
_clientManager = clientManager;
|
||||
}
|
||||
|
||||
[HttpGet("/api/monitor/status")]
|
||||
public IActionResult GetStatus()
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
var uptime = DateTime.UtcNow - StartTime;
|
||||
|
||||
var games = _gameManager.Games.ToList();
|
||||
var clients = _clientManager.Clients.ToList();
|
||||
|
||||
var status = new MonitorStatus
|
||||
{
|
||||
Status = "running",
|
||||
Timestamp = DateTime.UtcNow.ToString("o"),
|
||||
UptimeSeconds = (long)uptime.TotalSeconds,
|
||||
UptimeDisplay = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m",
|
||||
Version = GetVersion(),
|
||||
Runtime = RuntimeInformation.FrameworkDescription,
|
||||
Platform = RuntimeInformation.OSDescription,
|
||||
Processors = Environment.ProcessorCount,
|
||||
MemoryMb = process.WorkingSet64 / 1024 / 1024,
|
||||
GameCount = games.Count,
|
||||
PlayerCount = clients.Count(c => c.Player != null),
|
||||
ActiveConnections = clients.Count,
|
||||
Games = games.Select(g => new MonitorGame
|
||||
{
|
||||
Code = g.Code.ToString(),
|
||||
State = g.GameState.ToString(),
|
||||
Map = g.Options.Map.ToString(),
|
||||
PlayerCount = g.PlayerCount,
|
||||
Host = g.Host?.Character?.PlayerInfo?.PlayerName ?? "(none)",
|
||||
}).ToList(),
|
||||
};
|
||||
|
||||
return new JsonResult(status);
|
||||
}
|
||||
|
||||
[HttpGet("/api/monitor/health")]
|
||||
public IActionResult GetHealth()
|
||||
{
|
||||
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow.ToString("o") });
|
||||
}
|
||||
|
||||
private static string GetVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
var info = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
return info.ProductVersion ?? "unknown";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MonitorStatus
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string Timestamp { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("uptime_seconds")]
|
||||
public long UptimeSeconds { get; set; }
|
||||
|
||||
[JsonPropertyName("uptime_display")]
|
||||
public string UptimeDisplay { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("runtime")]
|
||||
public string Runtime { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("platform")]
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("processors")]
|
||||
public int Processors { get; set; }
|
||||
|
||||
[JsonPropertyName("memory_mb")]
|
||||
public long MemoryMb { get; set; }
|
||||
|
||||
[JsonPropertyName("game_count")]
|
||||
public int GameCount { get; set; }
|
||||
|
||||
[JsonPropertyName("player_count")]
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
[JsonPropertyName("active_connections")]
|
||||
public int ActiveConnections { get; set; }
|
||||
|
||||
[JsonPropertyName("games")]
|
||||
public List<MonitorGame> Games { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class MonitorGame
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public string State { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("map")]
|
||||
public string Map { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("player_count")]
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
[JsonPropertyName("host")]
|
||||
public string Host { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Net.Manager;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Empostor.Plugins.Monitor;
|
||||
|
||||
public sealed class MonitorMiddleware
|
||||
{
|
||||
private static readonly DateTime StartTime = Process.GetCurrentProcess().StartTime.ToUniversalTime();
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = null,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
};
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public MonitorMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, IGameManager gameManager, IClientManager clientManager)
|
||||
{
|
||||
var path = context.Request.Path;
|
||||
|
||||
if (path.Equals("/api/monitor/status", StringComparison.OrdinalIgnoreCase)
|
||||
&& HttpMethods.IsGet(context.Request.Method))
|
||||
{
|
||||
await WriteJsonAsync(context, BuildStatus(gameManager, clientManager));
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.Equals("/api/monitor/health", StringComparison.OrdinalIgnoreCase)
|
||||
&& HttpMethods.IsGet(context.Request.Method))
|
||||
{
|
||||
await WriteJsonAsync(context, new
|
||||
{
|
||||
status = "healthy",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private static MonitorStatus BuildStatus(IGameManager gameManager, IClientManager clientManager)
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
var uptime = DateTime.UtcNow - StartTime;
|
||||
var games = gameManager.Games.ToList();
|
||||
var clients = clientManager.Clients.ToList();
|
||||
|
||||
return new MonitorStatus
|
||||
{
|
||||
Status = "running",
|
||||
Timestamp = DateTime.UtcNow.ToString("o"),
|
||||
UptimeSeconds = (long)uptime.TotalSeconds,
|
||||
UptimeDisplay = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m",
|
||||
Version = GetVersion(),
|
||||
Runtime = RuntimeInformation.FrameworkDescription,
|
||||
Platform = RuntimeInformation.OSDescription,
|
||||
Processors = Environment.ProcessorCount,
|
||||
MemoryMb = process.WorkingSet64 / 1024 / 1024,
|
||||
GameCount = games.Count,
|
||||
PlayerCount = clients.Count(c => c.Player != null),
|
||||
ActiveConnections = clients.Count,
|
||||
Games = games.Select(g => new MonitorGame
|
||||
{
|
||||
Code = g.Code.ToString(),
|
||||
State = g.GameState.ToString(),
|
||||
Map = g.Options.Map.ToString(),
|
||||
PlayerCount = g.PlayerCount,
|
||||
Host = g.Host?.Character?.PlayerInfo?.PlayerName ?? "(none)",
|
||||
}).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task WriteJsonAsync(HttpContext context, object value)
|
||||
{
|
||||
context.Response.ContentType = "application/json; charset=utf-8";
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(value, JsonOptions));
|
||||
}
|
||||
|
||||
private static string GetVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = typeof(MonitorMiddleware).Assembly;
|
||||
var info = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
return info.ProductVersion ?? "unknown";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Plugins.Monitor;
|
||||
|
||||
public sealed class MonitorStatus
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string Timestamp { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("uptime_seconds")]
|
||||
public long UptimeSeconds { get; set; }
|
||||
|
||||
[JsonPropertyName("uptime_display")]
|
||||
public string UptimeDisplay { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("runtime")]
|
||||
public string Runtime { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("platform")]
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("processors")]
|
||||
public int Processors { get; set; }
|
||||
|
||||
[JsonPropertyName("memory_mb")]
|
||||
public long MemoryMb { get; set; }
|
||||
|
||||
[JsonPropertyName("game_count")]
|
||||
public int GameCount { get; set; }
|
||||
|
||||
[JsonPropertyName("player_count")]
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
[JsonPropertyName("active_connections")]
|
||||
public int ActiveConnections { get; set; }
|
||||
|
||||
[JsonPropertyName("games")]
|
||||
public List<MonitorGame> Games { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class MonitorGame
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public string State { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("map")]
|
||||
public string Map { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("player_count")]
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
[JsonPropertyName("host")]
|
||||
public string Host { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
@@ -12,11 +11,10 @@ public sealed class MonitorPluginStartup : IPluginStartup, IPluginHttpStartup
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddApplicationPart(typeof(MonitorPluginStartup).Assembly);
|
||||
}
|
||||
|
||||
public void ConfigureWebApplication(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMiddleware<MonitorMiddleware>();
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
[ApiController]
|
||||
[Route("/privacy")]
|
||||
public sealed class PrivacyController : ControllerBase
|
||||
{
|
||||
private readonly PrivacyStore _store;
|
||||
private readonly ILogger<PrivacyController> _logger;
|
||||
|
||||
public PrivacyController(PrivacyStore store, ILogger<PrivacyController> logger)
|
||||
{
|
||||
_store = store;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetPrivacy()
|
||||
{
|
||||
var html = _store.GetContent();
|
||||
return Content(html, "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/admin/api/privacy")]
|
||||
public async Task<IActionResult> UpdatePrivacy()
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(body);
|
||||
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
|
||||
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
|
||||
|
||||
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
|
||||
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
|
||||
?? "empostor";
|
||||
|
||||
if (token != adminToken)
|
||||
{
|
||||
return Unauthorized(new { error = "Invalid token." });
|
||||
}
|
||||
|
||||
_store.SaveContent(content);
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Empostor.Plugins.Privacy;
|
||||
|
||||
public sealed class PrivacyMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public PrivacyMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, PrivacyStore store)
|
||||
{
|
||||
var path = context.Request.Path;
|
||||
|
||||
if (path.Equals("/privacy", StringComparison.OrdinalIgnoreCase)
|
||||
&& HttpMethods.IsGet(context.Request.Method))
|
||||
{
|
||||
context.Response.ContentType = "text/html; charset=utf-8";
|
||||
await context.Response.WriteAsync(store.GetContent());
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.Equals("/admin/api/privacy", StringComparison.OrdinalIgnoreCase)
|
||||
&& HttpMethods.IsPost(context.Request.Method))
|
||||
{
|
||||
await HandleUpdateAsync(context, store);
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private static async Task HandleUpdateAsync(HttpContext context, PrivacyStore store)
|
||||
{
|
||||
using var reader = new StreamReader(context.Request.Body);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(body);
|
||||
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
|
||||
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
|
||||
|
||||
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
|
||||
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
|
||||
?? "empostor";
|
||||
|
||||
if (token != adminToken)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync("{\"error\":\"Invalid token.\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
store.SaveContent(content);
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync("{\"success\":true}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
context.Response.ContentType = "application/json";
|
||||
var error = JsonSerializer.Serialize(new { error = ex.Message });
|
||||
await context.Response.WriteAsync(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,10 @@ public sealed class PrivacyPluginStartup : IPluginStartup, IPluginHttpStartup
|
||||
{
|
||||
services.AddSingleton<PrivacyStore>();
|
||||
services.AddSingleton<IAdminExtension, PrivacyAdminExtension>();
|
||||
services.AddControllers()
|
||||
.AddApplicationPart(typeof(PrivacyPluginStartup).Assembly);
|
||||
}
|
||||
|
||||
public void ConfigureWebApplication(IApplicationBuilder app)
|
||||
{
|
||||
app.UseMiddleware<PrivacyMiddleware>();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Empostor.Api.Plugins;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -16,6 +17,8 @@ namespace Empostor.Server.Plugins
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext(typeof(PluginLoader));
|
||||
|
||||
internal static IReadOnlyList<Assembly> LoadedPluginAssemblies { get; private set; } = Array.Empty<Assembly>();
|
||||
|
||||
public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConfig config)
|
||||
{
|
||||
var assemblyInfos = new List<IAssemblyInformation>();
|
||||
@@ -105,6 +108,10 @@ namespace Empostor.Server.Plugins
|
||||
}
|
||||
|
||||
var orderedPlugins = LoadOrderPlugins(plugins);
|
||||
LoadedPluginAssemblies = orderedPlugins
|
||||
.Select(p => p.PluginType.Assembly)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
foreach (var plugin in orderedPlugins)
|
||||
{
|
||||
@@ -120,11 +127,50 @@ namespace Empostor.Server.Plugins
|
||||
{
|
||||
plugin.Startup?.ConfigureServices(services);
|
||||
}
|
||||
|
||||
EnsureMvcApplicationParts(services, orderedPlugins);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void EnsureMvcApplicationParts(IServiceCollection services, IReadOnlyList<PluginInformation> plugins)
|
||||
{
|
||||
var manager = services
|
||||
.LastOrDefault(d => d.ServiceType == typeof(ApplicationPartManager))
|
||||
?.ImplementationInstance as ApplicationPartManager;
|
||||
if (manager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureAssemblyPart(manager, typeof(PluginLoader).Assembly, insertFirst: true);
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
EnsureAssemblyPart(manager, plugin.PluginType.Assembly, insertFirst: false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureAssemblyPart(ApplicationPartManager manager, Assembly assembly, bool insertFirst)
|
||||
{
|
||||
if (manager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == assembly))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var part = new AssemblyPart(assembly);
|
||||
if (insertFirst)
|
||||
{
|
||||
manager.ApplicationParts.Insert(0, part);
|
||||
}
|
||||
else
|
||||
{
|
||||
manager.ApplicationParts.Add(part);
|
||||
}
|
||||
|
||||
Logger.Information("Registered MVC controllers from {Assembly}.", assembly.GetName().Name);
|
||||
}
|
||||
|
||||
private static void CheckPaths(IEnumerable<string> paths)
|
||||
{
|
||||
foreach (var path in paths)
|
||||
|
||||
@@ -33,6 +33,7 @@ using Empostor.Server.Service.Firewall;
|
||||
using Empostor.Server.Utils;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -269,7 +270,8 @@ namespace Empostor.Server
|
||||
{
|
||||
builder.ConfigureServices(s =>
|
||||
{
|
||||
s.AddControllers();
|
||||
s.AddControllers()
|
||||
.ConfigureApplicationPartManager(EnsureServerApplicationPart);
|
||||
});
|
||||
builder.Configure(app =>
|
||||
{
|
||||
@@ -289,5 +291,32 @@ namespace Empostor.Server
|
||||
|
||||
return hostBuilder;
|
||||
}
|
||||
|
||||
private static void EnsureServerApplicationPart(ApplicationPartManager manager)
|
||||
{
|
||||
EnsureAssemblyPart(manager, typeof(Program).Assembly, insertFirst: true);
|
||||
foreach (var assembly in PluginLoader.LoadedPluginAssemblies)
|
||||
{
|
||||
EnsureAssemblyPart(manager, assembly, insertFirst: false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureAssemblyPart(ApplicationPartManager manager, Assembly assembly, bool insertFirst)
|
||||
{
|
||||
if (manager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == assembly))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var part = new AssemblyPart(assembly);
|
||||
if (insertFirst)
|
||||
{
|
||||
manager.ApplicationParts.Insert(0, part);
|
||||
}
|
||||
else
|
||||
{
|
||||
manager.ApplicationParts.Add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user