diff --git a/src/UniversalDashboard.UITest/Integration/Scoping.Tests.ps1 b/src/UniversalDashboard.UITest/Integration/Scoping.Tests.ps1 index e99aea2a..d5035dae 100644 --- a/src/UniversalDashboard.UITest/Integration/Scoping.Tests.ps1 +++ b/src/UniversalDashboard.UITest/Integration/Scoping.Tests.ps1 @@ -10,7 +10,32 @@ Get-UDDashboard | Stop-UDDashboard $Global:MyVariable = "Test" +$Server = Start-UDDashboard -Port 10001 -Dashboard (New-UDDashboard -Title "Test" -Content {}) +$Driver = Start-SeFirefox + Describe "Variable Scoping" { + + Context "Variable set for session variable" { + $Dashboard = New-UDDashboard -Title "Test" -Content { + New-UDElement -Tag "div" -Endpoint { + $Session:SessionId = $SessionID + + New-UDElement -Tag 'div' -Id "output" -Endpoint { + $Session:SessionId + } + } + } + + $Server.DashboardService.SetDashboard($Dashboard) + Enter-SeUrl -Driver $Driver -Url "http://localhost:$BrowserPort" + + Start-sleep 2 + + It "should read session id from session variable" { + (Find-SeElement -Id 'output' -Driver $Driver).Text | should not be "" + } + } + Context "Invoke-Command test" { $Dashboard = New-UDDashboard -Title "Test" -Content { New-UDButton -Text 'Patch' -Id 'button' -OnClick ( @@ -26,18 +51,13 @@ Describe "Variable Scoping" { } } - $Server = Start-UDDashboard -Port 10001 -Dashboard $Dashboard - $Driver = Start-SeFirefox + $Server.DashboardService.SetDashboard($Dashboard) Enter-SeUrl -Driver $Driver -Url "http://localhost:$BrowserPort" - Start-Sleep 2 It "should start processes with click" { Find-SeElement -Id "button" -Driver $Driver | Invoke-SeClick (Find-SeElement -Id 'child' -Driver $Driver).Text | should be "child" } - - Stop-SeDriver $Driver - Stop-UDDashboard $Server } Context "Variable set global" { @@ -47,13 +67,11 @@ Describe "Variable Scoping" { } } - $Server = Start-UDDashboard -Port 10001 -Dashboard $Dashboard + $Server.DashboardService.SetDashboard($Dashboard) It "should return global variable" { (Invoke-RestMethod http://localhost:10001/api/internal/component/element/element) | should be "Test" } - - Stop-UDDashboard $Server } Context "Variable set local variable" { @@ -66,13 +84,11 @@ Describe "Variable Scoping" { } } - $Server = Start-UDDashboard -Port 10001 -Dashboard $Dashboard + $Server.DashboardService.SetDashboard($Dashboard) It "should return local variable" { (Invoke-RestMethod http://localhost:10001/api/internal/component/element/element) | should be "localVariable" } - - Stop-UDDashboard $Server } Context "Variable set loop variable" { @@ -85,7 +101,7 @@ Describe "Variable Scoping" { } } - $Server = Start-UDDashboard -Port 10001 -Dashboard $Dashboard + $Server.DashboardService.SetDashboard($Dashboard) It "should return loop variable" { 1..5 | ForEach-Object { @@ -93,8 +109,6 @@ Describe "Variable Scoping" { } } - - Stop-UDDashboard $Server } Context "Variable set for process" { @@ -115,10 +129,8 @@ Describe "Variable Scoping" { } } - $Server = Start-UDDashboard -Port 10001 -Dashboard $Dashboard - $Driver = Start-SeFirefox + $Server.DashboardService.SetDashboard($Dashboard) Enter-SeUrl -Driver $Driver -Url "http://localhost:$BrowserPort" - Start-Sleep 2 It "should stop processes with click" { Get-Process Notepad* | % { @@ -138,24 +150,8 @@ Describe "Variable Scoping" { (Get-Process Notepad* | Measure-Object).Count | Should be 0 } - - Stop-SeDriver $Driver - Stop-UDDashboard $Server - } - - Context "Variable set for session variable" { - $Dashboard = New-UDDashboard -Title "Test" -Content { - New-UDElement -Tag "div" -Endpoint { - $Session:Variables = @("1", "2,", "3") - } - New-UDElement -Tag 'div' -Id "output" - - foreach($Variable in $Session:Variables) { - New-UDButton -Text "ClickMe" -Id "btnClick$Variable" -OnClick { - Set-UDElement -Id "output" -Content { $Varible } - } - } - } } +} -} \ No newline at end of file +Stop-SeDriver $Driver +Stop-UDDashboard -Server $Server \ No newline at end of file diff --git a/src/UniversalDashboard/Controllers/ComponentController.cs b/src/UniversalDashboard/Controllers/ComponentController.cs index fa944973..e5635d4b 100644 --- a/src/UniversalDashboard/Controllers/ComponentController.cs +++ b/src/UniversalDashboard/Controllers/ComponentController.cs @@ -31,17 +31,17 @@ public class ComponentController : Controller private static readonly Logger Log = LogManager.GetLogger(nameof(ComponentController)); private readonly IExecutionService _executionService; private readonly IDashboardService _dashboardService; - private readonly AutoReloader _autoReloader; private readonly IMemoryCache _memoryCache; private readonly StateRequestService _stateRequestService; + private readonly ConnectionManager _connectionManager; - public ComponentController(IExecutionService executionService, IDashboardService dashboardService, IMemoryCache memoryCache, AutoReloader autoReloader, StateRequestService stateRequestService) + public ComponentController(IExecutionService executionService, IDashboardService dashboardService, IMemoryCache memoryCache, StateRequestService stateRequestService, ConnectionManager connectionManager) { _executionService = executionService; _dashboardService = dashboardService; - _autoReloader = autoReloader; _memoryCache = memoryCache; _stateRequestService = stateRequestService; + _connectionManager = connectionManager; } private async Task RunScript(Endpoint endpoint, Dictionary parameters = null, bool noSerialization = false) @@ -51,7 +51,8 @@ private async Task RunScript(Endpoint endpoint, Dictionary RunScript(Endpoint endpoint, Dictionary - /// This provider is the data accessor for shell variables. It uses - /// the HashtableProvider as the base class to get a hashtable as - /// a data store. + /// This is the session scope driver provider.null /// [CmdletProvider("Session", ProviderCapabilities.ShouldProcess)] [OutputType(typeof(PSVariable), ProviderCmdlet = ProviderCmdlet.SetItem)] @@ -27,22 +19,8 @@ namespace UniversalDashboard.Execution [OutputType(typeof(PSVariable), ProviderCmdlet = ProviderCmdlet.NewItem)] public sealed class SessionDriveVariableProvider : ContainerCmdletProvider, IContentCmdletProvider { - private readonly IMemoryCache _memoryCache; private static readonly Logger Logger = LogManager.GetLogger(nameof(SessionDriveVariableProvider)); - #region Constructor - - /// - /// The constructor for the provider that exposes variables to the user - /// as drives. - /// - public SessionDriveVariableProvider() - { - _memoryCache = ExecutionService.MemoryCache; - } // constructor - - #endregion Constructor - #region DriveCmdletProvider overrides /// @@ -68,7 +46,7 @@ protected override Collection InitializeDefaultDrives() return drives; } // InitializeDefaultDrives - private string ConnectionId + private string SessionId { get { @@ -78,26 +56,39 @@ private string ConnectionId protected override void GetItem(string name) { - var item = _memoryCache.Get(ConnectionId + name.ToLower()); - if (item != null) + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) { - base.WriteItemObject(item, name.ToLower(), false); + var value = EndpointService.Instance.Sessions[SessionId].GetVariableValue(name); + if (value != null) + { + base.WriteItemObject(value, name.ToLower(), false); + } } } protected override void NewItem(string path, string itemTypeName, object newItemValue) { - _memoryCache.Set(ConnectionId + path.ToLower(), newItemValue); + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) + { + EndpointService.Instance.Sessions[SessionId].SetVariable(path, newItemValue); + } } protected override void SetItem(string name, object value) { - _memoryCache.Set(ConnectionId + name.ToLower(), value); + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) + { + EndpointService.Instance.Sessions[SessionId].SetVariable(name, value); + } } protected override bool ItemExists(string path) { - return _memoryCache.TryGetValue(ConnectionId + path.ToLower(), out object val); + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) + { + return EndpointService.Instance.Sessions[SessionId].SessionVariables.ContainsKey(path.ToLower()); + } + return false; } protected override bool IsValidPath(string path) @@ -107,7 +98,10 @@ protected override bool IsValidPath(string path) public void ClearContent(string path) { - _memoryCache.Remove(ConnectionId + path.ToLower()); + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) + { + EndpointService.Instance.Sessions[SessionId].RemoveVariable(path); + } } public object ClearContentDynamicParameters(string path) @@ -119,7 +113,12 @@ public IContentReader GetContentReader(string path) { Logger.Debug($"GetContentReader - {path} "); - return new MemoryCacheContentReaderWriter(ConnectionId + path.ToLower(), _memoryCache); + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) + { + return new SessionStateReaderWriter(path.ToLower(), EndpointService.Instance.Sessions[SessionId]); + } + + return null; } public object GetContentReaderDynamicParameters(string path) @@ -131,7 +130,12 @@ public IContentWriter GetContentWriter(string path) { Logger.Debug($"GetContentWriter - {path} "); - return new MemoryCacheContentReaderWriter(ConnectionId + path.ToLower(), _memoryCache); + if (EndpointService.Instance.Sessions.ContainsKey(SessionId)) + { + return new SessionStateReaderWriter(path.ToLower(), EndpointService.Instance.Sessions[SessionId]); + } + + return null; } public object GetContentWriterDynamicParameters(string path) @@ -139,5 +143,59 @@ public object GetContentWriterDynamicParameters(string path) throw new NotImplementedException(); } #endregion DriveCmdletProvider overrides - } // VariableProvider + } + + public class SessionStateReaderWriter : IContentWriter, IContentReader + { + private readonly string _name; + private readonly Models.SessionState _sessionState; + + public SessionStateReaderWriter(string name, Models.SessionState sessionState) + { + _name = name; + _sessionState = sessionState; + } + + public void Close() + { + + } + + public void Dispose() + { + } + + public IList Read(long readCount) + { + var value = _sessionState.GetVariableValue(_name); + if (value != null) + { + return new ArrayList + { + value + }; + } + + return null; + } + + public void Seek(long offset, SeekOrigin origin) + { + + } + + public IList Write(IList content) + { + if (content.Count == 1) + { + _sessionState.SetVariable(_name, content[0]); + } + else + { + _sessionState.SetVariable(_name, content); + } + + return content; + } + } } diff --git a/src/UniversalDashboard/Models/Connection.cs b/src/UniversalDashboard/Models/Connection.cs new file mode 100644 index 00000000..92baa0ed --- /dev/null +++ b/src/UniversalDashboard/Models/Connection.cs @@ -0,0 +1,8 @@ +namespace UniversalDashboard.Models +{ + public class Connection + { + public string Id { get; set; } + public string SessionId { get; set; } + } +} diff --git a/src/UniversalDashboard/Models/SessionState.cs b/src/UniversalDashboard/Models/SessionState.cs index eb3b77d2..d7bd8425 100644 --- a/src/UniversalDashboard/Models/SessionState.cs +++ b/src/UniversalDashboard/Models/SessionState.cs @@ -7,6 +7,7 @@ public class SessionState public SessionState() { Endpoints = new Dictionary(); + SessionVariables = new Dictionary(); ConnectionIds = new List(); SyncRoot = new object(); } @@ -14,5 +15,47 @@ public SessionState() public object SyncRoot { get; set; } public List ConnectionIds { get; set; } public Dictionary Endpoints { get; set; } + public Dictionary SessionVariables { get; set; } + + public object GetVariableValue(string name) + { + name = name.ToLower(); + lock(SyncRoot) + { + if (SessionVariables.ContainsKey(name)) + { + return SessionVariables[name]; + } + return null; + } + } + + public void SetVariable(string name, object value) + { + name = name.ToLower(); + lock(SyncRoot) + { + if (SessionVariables.ContainsKey(name)) + { + SessionVariables[name] = value; + } + else + { + SessionVariables.Add(name, value); + } + } + } + + public void RemoveVariable(string name) + { + name = name.ToLower(); + lock(SyncRoot) + { + if (SessionVariables.ContainsKey(name)) + { + SessionVariables.Remove(name); + } + } + } } } diff --git a/src/UniversalDashboard/Server/DashboardHub.cs b/src/UniversalDashboard/Server/DashboardHub.cs index 1f63da40..955f8678 100644 --- a/src/UniversalDashboard/Server/DashboardHub.cs +++ b/src/UniversalDashboard/Server/DashboardHub.cs @@ -111,17 +111,19 @@ public static async Task Write(this IHubContext hub, string client public class DashboardHub : Hub { private IExecutionService _executionService; private readonly StateRequestService _stateRequestService; - private readonly IMemoryCache _memoryCache; + private readonly ConnectionManager _connectionManager; private readonly IDashboardService _dashboardService; + private readonly IMemoryCache _memoryCache; private static readonly Logger _logger = LogManager.GetLogger(nameof(DashboardHub)); - public DashboardHub(IExecutionService executionService, IMemoryCache memoryCache, StateRequestService stateRequestService, IDashboardService dashboardService) { + public DashboardHub(IExecutionService executionService, ConnectionManager connectionManager, StateRequestService stateRequestService, IDashboardService dashboardService, IMemoryCache memoryCache) { Log.Debug("DashboardHub constructor"); _executionService = executionService; _stateRequestService = stateRequestService; - _memoryCache = memoryCache; + _connectionManager = connectionManager; _dashboardService = dashboardService; + _memoryCache = memoryCache; } public override async Task OnConnectedAsync() @@ -140,21 +142,20 @@ public override async Task OnDisconnectedAsync(Exception exception) Log.Error(exception.Message); } - var sessionId = _memoryCache.Get(Context.ConnectionId); + var sessionId = _connectionManager.GetSessionId(Context.ConnectionId); if (sessionId != null) { - _memoryCache.Remove(sessionId); _dashboardService.EndpointService.EndSession(sessionId as string, Context.ConnectionId); } - _memoryCache.Remove(Context.ConnectionId); + _connectionManager.RemoveConnection(Context.ConnectionId); } public async Task SetSessionId(string sessionId) { Log.Debug($"SetSessionId({sessionId})"); - _memoryCache.Set(Context.ConnectionId, sessionId); + _connectionManager.AddConnection(new Connection { Id = Context.ConnectionId, SessionId = sessionId }); _dashboardService.EndpointService.StartSession(sessionId, Context.ConnectionId); await Clients.All.SendAsync("setConnectionId", Context.ConnectionId); @@ -176,10 +177,12 @@ public async Task RequestStateResponse(string requestId, Element state) public async Task UnregisterEvent(string eventId) { + await Task.CompletedTask; + Log.Debug($"UnregisterEvent() {eventId}"); - await Task.CompletedTask; - if (_memoryCache.TryGetValue(Context.ConnectionId, out string sessionId)) + var sessionId = _connectionManager.GetSessionId(Context.ConnectionId); + if (sessionId != null) { _dashboardService.EndpointService.Unregister(eventId, sessionId); } @@ -214,13 +217,14 @@ public Task ClientEvent(string eventId, string eventName, string eventData, stri { variables.Add("EventData", eventData); } + + variables.Add("UDConnectionManager", _connectionManager); variables.Add("EventId", eventId); - variables.Add("MemoryCache", _memoryCache); try { - _memoryCache.TryGetValue(Context.ConnectionId, out string sessionId); + var sessionId = _connectionManager.GetSessionId(Context.ConnectionId); var endpoint = _dashboardService.EndpointService.Get(eventId, sessionId); if (endpoint == null) @@ -249,7 +253,6 @@ public Task ClientEvent(string eventId, string eventName, string eventData, stri } catch (Exception ex) { - _logger.Error("Failed to execute action. " + ex.Message); throw; } diff --git a/src/UniversalDashboard/Server/ServerStartup.cs b/src/UniversalDashboard/Server/ServerStartup.cs index 227dc53e..2a092c89 100644 --- a/src/UniversalDashboard/Server/ServerStartup.cs +++ b/src/UniversalDashboard/Server/ServerStartup.cs @@ -55,6 +55,7 @@ public void ConfigureServices(IServiceCollection services) services.AddCors(); services.AddDirectoryBrowser(); services.AddSingleton(ExecutionService.MemoryCache); + services.AddSingleton(new ConnectionManager()); services.AddMvc().AddJsonOptions(x => { x.SerializerSettings.ContractResolver = new CustomContractResolver(); }); diff --git a/src/UniversalDashboard/Services/ConnectionManager.cs b/src/UniversalDashboard/Services/ConnectionManager.cs new file mode 100644 index 00000000..54b517ec --- /dev/null +++ b/src/UniversalDashboard/Services/ConnectionManager.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using UniversalDashboard.Models; + +namespace UniversalDashboard.Services +{ + public class ConnectionManager + { + public Dictionary Connections { get; private set; } + private static object Sync = new object(); + + public ConnectionManager() + { + Connections = new Dictionary(); + } + + public void AddConnection(Connection connection) + { + lock(Sync) + { + Connections.Add(connection.Id.ToLower(), connection); + } + } + + public void RemoveConnection(string id) + { + lock(Sync) + { + Connections.Remove(id.ToLower()); + } + } + + public string GetSessionId(string id) + { + lock(Sync) + { + if (Connections.ContainsKey(id.ToLower())) + { + return Connections[id.ToLower()].SessionId; + } + } + + return null; + } + } +} diff --git a/src/UniversalDashboard/Services/UDRunspaceFactory.cs b/src/UniversalDashboard/Services/UDRunspaceFactory.cs index b9fe844c..70838d95 100644 --- a/src/UniversalDashboard/Services/UDRunspaceFactory.cs +++ b/src/UniversalDashboard/Services/UDRunspaceFactory.cs @@ -129,4 +129,5 @@ public void Dispose() } #endregion } + }