From c1b002723108adf3c6a08a3009c4c056ba490222 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 30 Jan 2021 21:55:29 +0100 Subject: [PATCH] Plugin features - Added PluginFeature attribute Plugin features -Added PluginFeatureInfo to plugin features Web server - Moved REST APIs to plugins --- .../Artemis.Core.csproj.DotSettings | 1 + .../Plugins/DataModelExpansions/DataModel.cs | 3 + src/Artemis.Core/Plugins/Plugin.cs | 1 - src/Artemis.Core/Plugins/PluginFeature.cs | 5 + .../Plugins/PluginFeatureAttribute.cs | 27 +++ src/Artemis.Core/Plugins/PluginFeatureInfo.cs | 92 +++++++++ .../Services/PluginManagementService.cs | 5 + .../Controllers/PluginsController.cs | 35 ---- .../Services/WebServer/WebServerService.cs | 5 +- src/Artemis.UI/Bootstrapper.cs | 5 +- .../RemoteController.cs | 2 +- .../Tabs/General/GeneralSettingsTabView.xaml | 178 ++++++++++-------- .../General/GeneralSettingsTabViewModel.cs | 51 +++-- .../Tabs/Plugins/PluginFeatureView.xaml | 8 +- .../Tabs/Plugins/PluginFeatureViewModel.cs | 32 +--- .../Services/RegistrationService.cs | 12 +- .../Interfaces/IRemoteManagementService.cs | 6 - .../RemoteManagementService.cs | 12 -- 18 files changed, 297 insertions(+), 183 deletions(-) create mode 100644 src/Artemis.Core/Plugins/PluginFeatureAttribute.cs create mode 100644 src/Artemis.Core/Plugins/PluginFeatureInfo.cs delete mode 100644 src/Artemis.Core/Services/WebServer/Controllers/PluginsController.cs rename src/Artemis.UI/{Services/RemoteManagement => Controllers}/RemoteController.cs (96%) delete mode 100644 src/Artemis.UI/Services/RemoteManagement/Interfaces/IRemoteManagementService.cs delete mode 100644 src/Artemis.UI/Services/RemoteManagement/RemoteManagementService.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 61728d988..b1ea041c1 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -68,6 +68,7 @@ True True True + True True True True \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index 27418f2d1..e207c18eb 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using Artemis.Core.Modules; using Humanizer; +using Newtonsoft.Json; namespace Artemis.Core.DataModelExpansions { @@ -28,12 +29,14 @@ namespace Artemis.Core.DataModelExpansions /// /// Gets the plugin feature this data model belongs to /// + [JsonIgnore] [DataModelIgnore] public DataModelPluginFeature Feature { get; internal set; } /// /// Gets the describing this data model /// + [JsonIgnore] [DataModelIgnore] public DataModelPropertyAttribute DataModelDescription { get; internal set; } diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 853649b0b..0575a80ac 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -44,7 +44,6 @@ namespace Artemis.Core /// public DirectoryInfo Directory { get; } - /// /// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins /// diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index 733c3ee9b..b0e08554f 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -13,6 +13,11 @@ namespace Artemis.Core private bool _isEnabled; private Exception? _loadException; + /// + /// Gets the plugin feature info related to this feature + /// + public PluginFeatureInfo Info { get; internal set; } = null!; // Will be set right after construction + /// /// Gets the plugin that provides this feature /// diff --git a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs new file mode 100644 index 000000000..0bb78fb75 --- /dev/null +++ b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents an attribute that describes a plugin feature + /// + [AttributeUsage(AttributeTargets.Class)] + public class PluginFeatureAttribute : Attribute + { + /// + /// Gets or sets the user-friendly name for this property, shown in the UI. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the user-friendly description for this property, shown in the UI. + /// + public string? Description { get; set; } + + /// + /// The plugins display icon that's shown in the settings see for + /// available icons + /// + public string? Icon { get; set; } + } +} diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs new file mode 100644 index 000000000..7fe5a20de --- /dev/null +++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs @@ -0,0 +1,92 @@ +using Artemis.Core.DataModelExpansions; +using Artemis.Core.DeviceProviders; +using Artemis.Core.LayerBrushes; +using Artemis.Core.LayerEffects; +using Artemis.Core.Modules; +using Humanizer; +using Newtonsoft.Json; + +namespace Artemis.Core +{ + /// + /// Represents basic info about a plugin feature and contains a reference to the instance of said feature + /// + [JsonObject(MemberSerialization.OptIn)] + public class PluginFeatureInfo : CorePropertyChanged + { + private string? _description; + private string? _icon; + private string _name = null!; + private PluginFeature _pluginFeature = null!; + + internal PluginFeatureInfo() + { + } + + internal PluginFeatureInfo(PluginFeature instance, PluginFeatureAttribute? attribute) + { + Name = attribute?.Name ?? instance.GetType().Name.Humanize(LetterCasing.Title); + Description = attribute?.Description; + Icon = attribute?.Icon; + PluginFeature = instance; + + if (Icon != null) return; + Icon = PluginFeature switch + { + BaseDataModelExpansion => "TableAdd", + DeviceProvider => "Devices", + ProfileModule => "VectorRectangle", + Module => "GearBox", + LayerBrushProvider => "Brush", + LayerEffectProvider => "AutoAwesome", + _ => "Plugin" + }; + } + + /// + /// The name of the plugin + /// + [JsonProperty(Required = Required.Always)] + public string Name + { + get => _name; + internal set => SetAndNotify(ref _name, value); + } + + /// + /// A short description of the plugin + /// + [JsonProperty] + public string? Description + { + get => _description; + set => SetAndNotify(ref _description, value); + } + + /// + /// The plugins display icon that's shown in the settings see for + /// available icons + /// + [JsonProperty] + public string? Icon + { + get => _icon; + set => SetAndNotify(ref _icon, value); + } + + /// + /// Gets the plugin this info is associated with + /// + public PluginFeature PluginFeature + { + get => _pluginFeature; + internal set => SetAndNotify(ref _pluginFeature, value); + } + + /// + public override string ToString() + { + return PluginFeature.Id; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 39ef62b89..c15122d1d 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -9,6 +9,7 @@ using Artemis.Core.DeviceProviders; using Artemis.Core.Ninject; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; +using Humanizer; using McMaster.NETCore.Plugins; using Ninject; using Ninject.Extensions.ChildKernel; @@ -342,6 +343,10 @@ namespace Artemis.Core.Services // Include Plugin as a parameter for the PluginSettingsProvider IParameter[] parameters = {new Parameter("Plugin", plugin, false)}; PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureType, parameters); + + // Get the PluginFeature attribute which contains extra info on the feature + PluginFeatureAttribute? pluginFeatureAttribute = (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)); + instance.Info = new PluginFeatureInfo(instance, pluginFeatureAttribute); plugin.AddFeature(instance); // Load the enabled state and if not found, default to true diff --git a/src/Artemis.Core/Services/WebServer/Controllers/PluginsController.cs b/src/Artemis.Core/Services/WebServer/Controllers/PluginsController.cs deleted file mode 100644 index 7c4e8c29e..000000000 --- a/src/Artemis.Core/Services/WebServer/Controllers/PluginsController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EmbedIO; -using EmbedIO.Routing; -using EmbedIO.WebApi; - -namespace Artemis.Core.Services -{ - internal class PluginsController : WebApiController - { - private readonly IWebServerService _webServerService; - - public PluginsController(IWebServerService webServerService) - { - _webServerService = webServerService; - } - - [Route(HttpVerbs.Get, "/plugins/endpoints")] - public IReadOnlyCollection GetPluginEndPoints() - { - return _webServerService.PluginsModule.PluginEndPoints; - } - - [Route(HttpVerbs.Get, "/plugins/endpoints/{plugin}/{endPoint}")] - public PluginEndPoint GetPluginEndPoint(Guid plugin, string endPoint) - { - PluginEndPoint? pluginEndPoint = _webServerService.PluginsModule.PluginEndPoints.FirstOrDefault(e => e.PluginFeature.Plugin.Guid == plugin && e.Name == endPoint); - if (pluginEndPoint == null) - throw HttpException.NotFound(); - - return pluginEndPoint; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 79f7959d7..41b3911de 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -52,8 +52,6 @@ namespace Artemis.Core.Services .HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception)) .HandleUnhandledException(JsonExceptionHandlerCallback); - // Add built-in core controllers to the API module - apiModule.RegisterController(() => _kernel.Get()); // Add registered controllers to the API module foreach (WebApiControllerRegistration registration in _controllers) apiModule.RegisterController(registration.ControllerType, (Func) registration.UntypedFactory); @@ -177,7 +175,8 @@ namespace Artemis.Core.Services { context.Response.ContentType = MimeType.Json; await using TextWriter writer = context.OpenResponseText(); - await writer.WriteAsync(JsonConvert.SerializeObject(data)); + string json = JsonConvert.SerializeObject(data, new JsonSerializerSettings {PreserveReferencesHandling = PreserveReferencesHandling.Objects}); + await writer.WriteAsync(json); } private async Task HandleHttpExceptionJson(IHttpContext context, IHttpException httpException) diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 446cba8cb..5544c2307 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -87,8 +87,9 @@ namespace Artemis.UI } }); - Kernel.Get().RegisterInputProvider(); - Kernel.Get(); + IRegistrationService registrationService = Kernel.Get(); + registrationService.RegisterInputProvider(); + registrationService.RegisterControllers(); } protected override void ConfigureIoC(IKernel kernel) diff --git a/src/Artemis.UI/Services/RemoteManagement/RemoteController.cs b/src/Artemis.UI/Controllers/RemoteController.cs similarity index 96% rename from src/Artemis.UI/Services/RemoteManagement/RemoteController.cs rename to src/Artemis.UI/Controllers/RemoteController.cs index a39468924..2c8c793d5 100644 --- a/src/Artemis.UI/Services/RemoteManagement/RemoteController.cs +++ b/src/Artemis.UI/Controllers/RemoteController.cs @@ -5,7 +5,7 @@ using EmbedIO; using EmbedIO.Routing; using EmbedIO.WebApi; -namespace Artemis.UI.Services +namespace Artemis.UI.Controllers { public class RemoteController : WebApiController { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml index f7c14eebf..9f0564084 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml @@ -59,6 +59,28 @@ + + + + + + + + + + + Startup delay + + Set the amount of seconds to wait before running Artemis with Windows. + If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. + + + + + + + + @@ -89,62 +111,14 @@ - - Setup wizard - - Opens the startup wizard usually shown when Artemis first starts. + + Log level + + Sets the logging level, a higher logging level will result in more log files. - - - - - - - - - - - - - - - - Debugger - - Use the debugger to see the raw image Artemis is rendering on the surface. - - - - - - - - - - - - - - - - - - - Application files - - Opens the directory where application files like plugins and settings are stored. - - - - + @@ -170,28 +144,6 @@ - - - - - - - - - - - - - Log level - - Sets the logging level, a higher logging level will result in more log files. - - - - - - - @@ -418,6 +370,82 @@ + + + + Tools + + + + + + + + + + + + + Setup wizard + + Opens the startup wizard usually shown when Artemis first starts. + + + + + + + + + + + + + + + + + + + Debugger + + Use the debugger to see the raw image Artemis is rendering on the surface. + + + + + + + + + + + + + + + + + + + Application files + + Opens the directory where application files like plugins and settings are stored. + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index 9318f27f8..21a54084c 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -125,7 +125,19 @@ namespace Artemis.UI.Screens.Settings.Tabs.General _settingsService.GetSetting("UI.AutoRun", false).Value = value; _settingsService.GetSetting("UI.AutoRun", false).Save(); NotifyOfPropertyChange(nameof(StartWithWindows)); - Task.Run(ApplyAutorun); + Task.Run(() => ApplyAutorun(false)); + } + } + + public int AutoRunDelay + { + get => _settingsService.GetSetting("UI.AutoRunDelay", 15).Value; + set + { + _settingsService.GetSetting("UI.AutoRunDelay", 15).Value = value; + _settingsService.GetSetting("UI.AutoRunDelay", 15).Save(); + NotifyOfPropertyChange(nameof(AutoRunDelay)); + Task.Run(() => ApplyAutorun(true)); } } @@ -293,11 +305,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.General protected override void OnInitialActivate() { - Task.Run(ApplyAutorun); + Task.Run(() => ApplyAutorun(false)); base.OnInitialActivate(); } - private void ApplyAutorun() + private void ApplyAutorun(bool recreate) { if (!StartWithWindows) StartMinimized = false; @@ -307,23 +319,29 @@ namespace Artemis.UI.Screens.Settings.Tabs.General if (File.Exists(autoRunFile)) File.Delete(autoRunFile); + // TODO: Don't do anything if running a development build, only auto-run release builds + // Create or remove the task if necessary try { - Process schtasks = new() + bool taskCreated = false; + if (!recreate) { - StartInfo = + Process schtasks = new() { - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = true, - FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"), - Arguments = "/TN \"Artemis 2 autorun\"" - } - }; + StartInfo = + { + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"), + Arguments = "/TN \"Artemis 2 autorun\"" + } + }; - schtasks.Start(); - schtasks.WaitForExit(); - bool taskCreated = schtasks.ExitCode == 0; + schtasks.Start(); + schtasks.WaitForExit(); + taskCreated = schtasks.ExitCode == 0; + } if (StartWithWindows && !taskCreated) CreateAutoRunTask(); @@ -347,6 +365,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.General task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Author") .SetValue(System.Security.Principal.WindowsIdentity.GetCurrent().Name); + task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "Delay") + .SetValue(TimeSpan.FromSeconds(AutoRunDelay)); + task.Descendants().First(d => d.Name.LocalName == "Principals").Descendants().First(d => d.Name.LocalName == "Principal").Descendants().First(d => d.Name.LocalName == "UserId") .SetValue(System.Security.Principal.WindowsIdentity.GetCurrent().User.Value); @@ -369,7 +390,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General UseShellExecute = true, Verb = "runas", FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"), - Arguments = $"/Create /XML \"{xmlPath}\" /tn \"Artemis 2 autorun\"" + Arguments = $"/Create /XML \"{xmlPath}\" /tn \"Artemis 2 autorun\" /F" } }; diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index 9e88a2346..eb8f06d5f 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -7,6 +7,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" xmlns:converters="clr-namespace:Artemis.UI.Converters" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:PluginFeatureViewModel}"> @@ -21,8 +22,9 @@ - @@ -40,7 +42,7 @@ - + Feature.GetType().Name.Humanize(); - public Exception LoadException => Feature.LoadException; public bool Enabling @@ -109,7 +97,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } catch (Exception e) { - _messageService.ShowMessage($"Failed to enable {Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + _messageService.ShowMessage($"Failed to enable {Feature.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); } finally { @@ -123,20 +111,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } } - private PackIconKind GetIconKind() - { - return Feature switch - { - BaseDataModelExpansion => PackIconKind.TableAdd, - DeviceProvider => PackIconKind.Devices, - ProfileModule => PackIconKind.VectorRectangle, - Module => PackIconKind.GearBox, - LayerBrushProvider => PackIconKind.Brush, - LayerEffectProvider => PackIconKind.AutoAwesome, - _ => PackIconKind.Plugin - }; - } - #region Event handlers private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e) diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 6ae335bf4..c6c80e900 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Controllers; using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Input; using Artemis.UI.InputProviders; @@ -19,6 +20,7 @@ namespace Artemis.UI.Services private readonly IPluginManagementService _pluginManagementService; private readonly ISurfaceService _surfaceService; private readonly IInputService _inputService; + private readonly IWebServerService _webServerService; private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInPropertyEditors; @@ -28,7 +30,8 @@ namespace Artemis.UI.Services IProfileEditorService profileEditorService, IPluginManagementService pluginManagementService, ISurfaceService surfaceService, - IInputService inputService) + IInputService inputService, + IWebServerService webServerService) { _logger = logger; _dataModelUIService = dataModelUIService; @@ -36,6 +39,7 @@ namespace Artemis.UI.Services _pluginManagementService = pluginManagementService; _surfaceService = surfaceService; _inputService = inputService; + _webServerService = webServerService; LoadPluginModules(); pluginManagementService.PluginEnabling += PluginServiceOnPluginEnabling; @@ -91,6 +95,11 @@ namespace Artemis.UI.Services _inputService.AddInputProvider(new NativeWindowInputProvider(_logger, _inputService)); } + public void RegisterControllers() + { + _webServerService.AddController(); + } + private void PluginServiceOnPluginEnabling(object sender, PluginEventArgs e) { e.Plugin.Kernel.Load(new[] {new PluginUIModule(e.Plugin)}); @@ -109,5 +118,6 @@ namespace Artemis.UI.Services void RegisterBuiltInDataModelInputs(); void RegisterBuiltInPropertyEditors(); void RegisterInputProvider(); + void RegisterControllers(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/RemoteManagement/Interfaces/IRemoteManagementService.cs b/src/Artemis.UI/Services/RemoteManagement/Interfaces/IRemoteManagementService.cs deleted file mode 100644 index 25cafb0d3..000000000 --- a/src/Artemis.UI/Services/RemoteManagement/Interfaces/IRemoteManagementService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.UI.Services -{ - public interface IRemoteManagementService : IArtemisUIService - { - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Services/RemoteManagement/RemoteManagementService.cs b/src/Artemis.UI/Services/RemoteManagement/RemoteManagementService.cs deleted file mode 100644 index 956e8a474..000000000 --- a/src/Artemis.UI/Services/RemoteManagement/RemoteManagementService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Artemis.Core.Services; - -namespace Artemis.UI.Services -{ - public class RemoteManagementService : IRemoteManagementService - { - public RemoteManagementService(IWebServerService webServerService) - { - webServerService.AddController(); - } - } -} \ No newline at end of file