/// <summary> /// Creates a new session /// </summary> /// <returns>The ID of the new session</returns> protected string BeginSession() { lock (SessionCollectionInternalLock) { // Initializes a new session var newSession = new SessionInfo(); while (SessionCollectionInternal.ContainsKey(newSession.ID)) { // Keep pulling new sessions until it does not exists on the collection newSession = new SessionInfo(); } // Add the new session SessionCollectionInternal.Add(newSession.ID, newSession); // Returns the new session ID return(newSession.ID); } }
/// <summary> /// Finishes a given session by destroying all objects inside it /// </summary> /// <param name="sessionID">The ID Session</param> protected void EndSession(string sessionID) { lock (SessionCollectionInternalLock) { // Fetch Session if (SessionCollectionInternal.TryGetValue(sessionID, out var sessionInfo)) { // Loop thru objects and call dispose, if applicable foreach (var kvp in sessionInfo.ObjectCollection) { if (kvp.Value is IDisposable disposable) { disposable.Dispose(); } } // Remove session from collection SessionCollectionInternal.Remove(sessionID); } } }
/// <summary> /// Executes a command asynchronously /// </summary> /// <param name="sessionID">The ID of the session running the command</param> /// <param name="inputCommand">The full command input</param> /// <returns>A <see cref="CommandExecutionResponse"/> containing the results of the execution</returns> protected async Task <CommandExecutionResponse> ExecuteCommandAsync(string sessionID, string inputCommand) { try { // Notify CommandReceived event CommandReceived?.Invoke(this, new CommandReceivedEventArgs(inputCommand, sessionID)); // Parse Full Command, by separating what is the command from what are the arguments var ResultingCommand = this.CommandParser.Parse(inputCommand); // Try to assign each parameter var methodArgumentsList = new List <object>(); // If arguments are not specified, set an empty array to it if (ResultingCommand.Arguments == null) { ResultingCommand.Arguments = new ParsedCommandArgument[] { } } ; // Look for command if (CommandCollectionInternal.TryGetValue(ResultingCommand.Command, out var commandInfo)) { // Check for Help Call if (ResultingCommand.IsHelpRequest) { // Display the command help var commandHelpAttribute = commandInfo.GetCommandHelpAttribute(); if (commandHelpAttribute == null) { return(CommandExecutionResponse.Help("")); } return(CommandExecutionResponse.Help(commandHelpAttribute.Contents)); } else { // Check if required parameters have been informed, if they were not, display help (if available) // Assign Parameters if (ResultingCommand.IsArgumentParameterSpecific) { // The arguments have a parameter name or a identifier, set them in sequence to be used later var currentArgumentsDictionary = new Dictionary <string, ParsedCommandArgument>(); foreach (var r in ResultingCommand.Arguments) { if (currentArgumentsDictionary.ContainsKey(r.Parameter.ToLower())) { throw new Exception($"Parameter '{r.Parameter.ToLower()}' is repeated on the syntax"); } currentArgumentsDictionary.Add(r.Parameter.ToLower(), r); } var newArgumentsList = new List <ParsedCommandArgument>(); foreach (var p in commandInfo.ParametersInfo) { if (currentArgumentsDictionary.TryGetValue(p.Name.ToLower(), out var parsedCommandArgument)) { throw new Exception($"Parameter '{p.Name.ToLower()}' is not informed"); } newArgumentsList.Add(parsedCommandArgument); } ResultingCommand.Arguments = newArgumentsList.ToArray(); } // Make sure the number of arguments match the method argument number if (ResultingCommand.Arguments.Length == commandInfo.ParametersInfo.Length) { for (int iArgument = 0; iArgument < ResultingCommand.Arguments.Length; iArgument++) { // Get Variables var param = commandInfo.ParametersInfo[iArgument]; var argument = ResultingCommand.Arguments[iArgument]; // Try to apply parameter try { if (param.ParameterType == typeof(string)) { // A direct conversion is available methodArgumentsList.Add(argument.Value); } else { // There is no direct conversion, try to apply the parameter using the Convert object methodArgumentsList.Add(Convert.ChangeType(argument, param.ParameterType)); } } catch (Exception eAssignment) { // Throw an error with the invalid argument throw new InvalidCommandArgumentsException(ResultingCommand.Command, param.Name, param.ParameterType, argument.Value, eAssignment); } } } else { // Number of arguments do not match, return help var argumentException = new ArgumentException("The number of arguments needed for the command does not match the number of arguments provided"); var commandHelpAttribute = commandInfo.GetCommandHelpAttribute(); if (commandHelpAttribute == null) { return(CommandExecutionResponse.Help("", DefaultErrorStatus, argumentException)); } return(CommandExecutionResponse.Help(commandHelpAttribute.Contents, DefaultErrorStatus, argumentException)); } // Arguments are set // Define the CommandClass where the command will be executed against var commandObject = (object)null; // Look for Session Information to start fetching / creating objects if (SessionCollectionInternal.TryGetValue(sessionID, out SessionInfo sessionInfo)) { // Session information found // Retrieve command class info if (CommandClassCollectionInternal.TryGetValue(commandInfo.CommandClassType, out var commandClassInfo)) { // Check the CommandClass lifetime before trying to find the object where to run the command against if (commandClassInfo.CommandClassAttribute.Lifetime == InstanceLifetimes.Singleton) { // Singleton lifetime, only one instance of the object should exist // Make sure to lock the dictionary to force other threads to hold lock (SingletonObjectsCollectionInternalLock) { // Singleton - Object should be on the SingletonObjectCollectionInternal if (!(SingletonObjectsCollectionInternal.TryGetValue(commandInfo.CommandClassType, out commandObject))) { // Singleton object could not be found, then create it and add back to the collection // Create the Command Object commandObject = commandClassInfo.CreateInstance(); // Adds the singleton object to the collection SingletonObjectsCollectionInternal.Add(commandInfo.CommandClassType, commandObject); } } } else if (commandClassInfo.CommandClassAttribute.Lifetime == InstanceLifetimes.Scoped) { // Scoped lifetime, only one instance per session should exist // Check object on the Session Info if (!sessionInfo.ObjectCollection.TryGetValue(commandInfo.CommandClassType, out commandObject)) { // Create and add object to the session lock (SessionCollectionInternalLock) { // Create the Command Object and add to collection commandObject = commandClassInfo.CreateInstance(); sessionInfo.ObjectCollection.Add(commandInfo.CommandClassType, commandObject); } } } else { // Transient lifetime, initializes it everytime commandObject = commandClassInfo.CreateInstance(); } } else { // Cannot find registration details for command class throw new Exception($"Unable to find registration details for type '{commandInfo.CommandClassType.Name}'"); } } else { // Error, unable to find the session throw new Exception($"Session {sessionID} cannot be found"); } try { // Calls the Command Asynchronously var result = await Task.Factory.StartNew <object>(() => { return(commandInfo.MethodInfo.Invoke(commandObject, methodArgumentsList.ToArray())); } ).ConfigureAwait(false); // Depending on the type of result, throw response if (result is CommandExecutionResponse cr) { return(cr); } else { // Command executed without exceptions, create a Success Response with no data return(CommandExecutionResponse.Success(DefaultSuccessStatus, result)); } } catch (Exception eExecution) { // Throws the execution exception ahead throw new CommandInExecutionException(ResultingCommand.Command, methodArgumentsList.ToArray(), eExecution); } } } else { // Command not found, throw exception throw new CommandNotFoundException(ResultingCommand.Command); } } catch (InvalidCommandArgumentsException e) { return(CommandExecutionResponse.Error(DefaultErrorStatus, e)); } catch (CommandNotFoundException) { return(CommandExecutionResponse.NotFound(DefaultNotFoundStatus)); } catch (Exception e) { return(CommandExecutionResponse.Error(DefaultErrorStatus, e)); } }