WebLogger.Crestron
1.3.0
dotnet add package WebLogger.Crestron --version 1.3.0
NuGet\Install-Package WebLogger.Crestron -Version 1.3.0
<PackageReference Include="WebLogger.Crestron" Version="1.3.0" />
paket add WebLogger.Crestron --version 1.3.0
#r "nuget: WebLogger.Crestron, 1.3.0"
// Install WebLogger.Crestron as a Cake Addin #addin nuget:?package=WebLogger.Crestron&version=1.3.0 // Install WebLogger.Crestron as a Cake Tool #tool nuget:?package=WebLogger.Crestron&version=1.3.0
Weblogger
The WebLogger is a websocket server designed to provide an accessible console application served to an html user interface. The WebLogger library targets .netstandard 2.0 and can be used in any .net framework 4.7 and .net Core application. WebLogger will manage the server and provide an easy way to create a custom CLI using commands and prompts. This library also includes an HTML front end using vanilla JS to handle the user interface component. The webpage is embedded into the DLL and will be extracted to a destination of your choosing when executed.
Inspiration for this project comes from https://kielthecoder.com/2021/04/16/vc-4-websocket-sharp/. This was original created to solve the problems described in his blog regarding Crestron's Virtual Control platform. However it has since proven usefull in other application so a move to .netstandard was important.
Table of Contents
- VS Solution
- WebLogger
- Commands
- Embedded HTML
- Serilog Sink
- Source Generators
- HTML Rendering
- Release Notes
Visual Studio Solution
The included solution includes five projects including two example projects and 4 libraries.
- WebLogger
- WebLogger.Serilog
- WebLogger.Crestron
- WebLogger.Generators
- Console Application
- Crestron Application
Unit test project is also included and located in the tests directory. This area needs Improvement
WebLogger.csproj
This is lowest level library including all WebLogger types and logic. This library has one dependency on WebSocketSharp
WebLogger.Serilog.csproj
The WebLogger.Serilog project provides a serilog sink used to write structured logging outputs to the WebLogger console. Included in this project are the WebLogger Sink and Extension methods to streamline the configuration and implmentation.
WebLogger.Crestron.csproj
Adds a reference to the Crestron SDK. This project provides some helpful Crestron commands to use with the console. Since browsers will block ws when the html page is served via https this server solves (albeit unsecured) this issue by providing an unsecured http server to distribute the HTML files
WebLogger.Generators.csproj
Provides source generators used to create commands and (other cool stuff in the future).
WebLogger.ConsoleApp Example Program
The WebLogger example is a simple console application showing SerilogSink usage.
WebLogger.CrestronApp Example Program
The WebLogger example is a Crestron SDK SimpleSharp program that demonstrates how to instantiate the WebLogger
class and add console commands with callbacks.
Create a WebLogger
To Create a new instance of the WebLoger
class included in the using WebLogger
namespace.
Creating a new instace will:
- Create a Websocket Server at the specified port
- Copy all embedded resource HTML files to your local file system at the specified directory
- Create a few default console commands
using WebLogger;
Create a new instance and start the server using the WebLoggerFactory
. The default factory method will
return an IWebLogger
interface using the default WebLogger
concrete implmentation.
var logger = WebLoggerFactory.CreateWebLogger();
Optionally use the Lambda Action<WebLoggerOptions
to override the default parameters.
Note: Currently secured web sockets are not fully supported, options to provide a valid certificate will be provided in the next release.
https://github.com/ewilliams0305/WebLogger/issues/7
var logger = WebLoggerFactory.CreateWebLogger(options =>
{
options.Secured = false;
options.WebSocketTcpPort = 54321; //allows you to provide a TCP port used by the web socket server
options.DestinationWebpageDirectory = "C:/Temp/"; //allows you to provide a file directory to extract the embedded html files.
options.Commands = commands; //provide a collection of custom commands that will be registered with the console.
options.Colors.ProvideColors( //provide colors for all rendered messages.
verbose: Color.Blue,
information: Color.DarkOrange,
error: Color.Chocolate);
});
Call the start method to extract the embedded resources and start the web socket server at the specified port.
logger.Start();
Console Commands
Custom console commands can be created and registered with the logger, after all this is the whole point of CLI.
To get started using custom commands create a class that implements the IWebLoggerCommand
interface or
using the provided WebLoggerCommand
concrete class. This class has been provided for your convenience and can be used to create ADHOC commands.
Create a Command
Define a name for the console command. This string will be used as the command key and is what the user would enter the webpage user interface.
A friendly description and help should be provided and should be descriptive informing the user as to the function of the command.
When the WebLogger input receives a matching command key the CommandHandler
will be executed.
The command handler method will provide your class the Command
and a list of args
. Args will include a collection of strings that were entered into the CLI command line after the command string.
Example: enter "DO A Arg With Another Arg" would provide the handler a new List<string>{A, Arg, With, Another, Arg}
including all values.
internal class DoWorkCommand : IWebLoggerCommand
{
public string Command => "DO";
public string Description => "Does work";
public string Help => "Does lots of stuff";
public Func<string, List<string>, ICommandResponse> CommandHandler => DoTheWork;
public ICommandResponse DoTheWork(string command, List<string> args)
{
return CommandResponse.Success(this, "Done the Work");
}
}
Use the provided WebLoggerCommand
class.
var command = new ConsoleCommand(
"EXAMPLE",
"Simple example of console command",
"Parameter: NA",
(cmd, args) =>
{
Log.Logger.Information("{command} Received", cmd);
});
var command = new ConsoleCommand(
"EXAMPLE",
"Simple example of console command",
"Parameter: NA",
IWebLoggerCommandHandler);
All command handlers must response to the WebLogger with a Success, Failure, or Error.
The ICommandResponse
type can be implemented and custom responses can be provided to the WebLogger.
A default implementation is provided in the library with several factory methods.
public ICommandResponse HandleCommand(string command, List<string> args)
{
if (args == null || args.Count == 0)
{
return CommandResponse.Failure(this, "Missing File Path Parameter");
}
try
{
EmbeddedResources.ExtractEmbeddedResource(
Assembly.GetAssembly(typeof(IAssemblyMarker)),
ConstantValues.HtmlRoot,
args[0]);
return CommandResponse.Success(this, $"{args[0]}/index.html");
}
catch (FileLoadException fileLoadException)
{
CommandResponse.Error(this, fileLoadException);
}
catch (FileNotFoundException fileNotFoundException)
{
CommandResponse.Error(this, fileNotFoundException);
}
catch (IOException ioException)
{
CommandResponse.Error(this, ioException);
}
return CommandResponse.Error(this, new Exception("Code Unreachable"));
}
Responses are formatted and presented back to the WebLogger webpage with color formatted strings. Commands with prompts will be implemented in a future release https://github.com/ewilliams0305/WebLogger/issues/8
Register Console Commands
After creating commands, they will need to be registered with the WebLogger instance. Programs can contain multiple WebLogger servers with different commands registered with each server.
At the simplest form execute the IWebLogger.RegisterCommand(IWebLoggerCommand)
method.
var logger = WebLoggerFactory.CreateWebLogger();
var command = new WebLoggerCommand(
(cmd, args) => CommandResponse.Success("TEST", $"{cmd} Received"),
"TEST",
"Simple example of console command",
"Parameter: NA")
logger.RegisterCommand(logger);
Commands can also be removed from the CLI at anytime.
logger.RemoveCommand(logger);
Extension methods have been provided to make this easier. If your custom command has a paramterless constructor you can execute the assembly discovery extension method to find all IWebLoggerCommands
in the provided assembly.
logger.DiscoverCommands(Assembly.GetAssembly(typeof(Program)))
An extension method to discover all the commands created inside the WebLogger library can be used as well
logger.DiscoverProvidedCommands();
There is even an extension method in the Crestron implementation to discover the provided Crestron commands
logger.DiscoverCrestronCommands();
An extension method to register a collection of commands has also been provided.
var logger = WebLoggerFactory.CreateWebLogger();
var commands = new List<IWebLoggerCommand>()
{
new WebLoggerCommand(
(cmd, args) => CommandResponse.Success("EXAMPLE", $"{cmd} Received"),
"EXAMPLE",
"Simple example of console command",
"Parameter: NA"),
new WebLoggerCommand(
(cmd, args) => CommandResponse.Success("TEST", $"{cmd} Received"),
"TEST",
"Simple example of console command",
"Parameter: NA")
};
logger.RegisterCommands(commands);
Embedded HTML
Located in the WebLogger
project is a folder titled HTML
. All HTML source files have been added to the project and configured as an embedded resource.
These files will be automatically extracted and written to the provided applicationDirectory
in the WebLoggerFactory's CreateWebLogger() method.
var logger = WebLoggerFactory.CreateWebLogger(options =>
{
options.DestinationWebpageDirectory = "C:/Temp/";
});
Be aware, the program will check if the files are already created and ONLY write them if they are not found. This decision was made to reduce disc writes and ensure files are recreated every time the application is restarted This means the HTML files will need to be deleted off the server if changes to the HTML are made. After starting the WebLogger, a directory will be created at the specifed location with the following file tree
As of version 1.1.7 the info.txt file will be parsed and compared. If the loaded version is differnt that the running verson the webpage will be replaced. this will help keep the front-end syncronized with the backed. The update command before is still functional for legacy purposes and debugging
- index.html
- console.js
- style.css
- info.txt
To help with writing webpage updates a custom command is provided by the IWebLogger.DiscoverProvidedCommands();
extension method. Execute this command and your weblogger will be provided with an Update
command. All files will be extracted from the embedded resources and written to the specified file directory.
COMMAND............... | HELP........................................................
> UPDATE.............. | FORCES THE WEBPAGE UPDATE AND OVERWRITES THE EXISTING WEBPAGE | Parameter: Destination File Path |
> HELP................ | RETURNS ALL REGISTERED WEBLOGGER CONSOLE COMMANDS | Parameter: NA |
> IPCONFIG............ | DISPLAYS THE IP CONFIGURATION........... | Not parameters |
HELP>Help End
update /opt/crestron/virtualcontrol/RunningPrograms/TEST/Html/logger/
UPDATE>/opt/crestron/virtualcontrol/RunningPrograms/TEST/Html/logger//index.html
When using the WebLogger.Crestron library you can create a custom http file server and distibute the HTML page via an unsecured webserver
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink(
options =>
{
options.Commands = commands;
options.Secured = false;
options.DestinationWebpageDirectory = Path.Combine(Directory.GetApplicationRootDirectory(), "html/logger");
options.WebSocketTcpPort = 54321;
options.Colors.ProvideColors(
verbose: Color.Blue,
information: Color.DarkOrange,
error: Color.Chocolate);
},
logger =>
{
logger
.ServeWebLoggerHtml(8081)
.DiscoverCrestronCommands();
})
.CreateLogger();
The above code will result in a url http://ip:8081/index.html While the files will be stored in the application directory /html/logger/
Serilog Sink
The WebLogger.Serilog library provides a Serilog Sink used to write structured logs to the WebLogger websocket output.
Generally speaking, this is really intended for verbose information as the WebLogger does not store data. Logs should be writen to additional Sinks for permanent storage.
Start by creating a LoggerConfiguration
and WriteTo
the WebLoggerSink
.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink()
.CreateLogger();
Invoking the WriteTo.WebLoggerSink()
method will create a weblogger server and start the services with default WebLoggerOptions
.
To further customize you configuration implement the Action<WebLoggerOptions>
lambda and update the properties to meet your configuration needs.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink(
options =>
{
options.Commands = commands;
options.Secured = false;
options.DestinationWebpageDirectory = "C:/Temp/WebLogger/Logger";
options.WebSocketTcpPort = 54321;
}
.CreateLogger();
Since the Sink is now resonsible for creation of the WebLogger and your application no longer has a reference to the logger an optional
Action<IWebLogger>
has been provided. This provides you a reference to the WebLogger and allows for further command registration and management.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink(
options =>
{
options.Commands = commands;
options.Secured = false;
options.DestinationWebpageDirectory = "C:/Temp/WebLogger/Logger";
options.WebSocketTcpPort = 54321;
},
logger =>
{
logger.DiscoverCommands(Assembly.GetAssembly(typeof(Program)))
.DiscoverProvidedCommands();
})
.CreateLogger();
If you need to, store a referance to the logger, but NOTE Log.CloseAndFlush()
will dispose the WebLogger
and could create upstream issues with your application depending on the usage.
IWebLogger webLogger;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink(logger => webLogger = logger;)
.CreateLogger();
webLogger
.DiscoverCommands(Assembly.GetAssembly(typeof(Program)))
.DiscoverProvidedCommands();
A custom command has also been provided and can be registered to control the logging minimum logging level. Create an instance of the LogLevelCommand
and register it with the logger.
var logLevelCommand = new LoggerLevelCommand(LogEventLevel.Verbose);
Log.Logger = new LoggerConfiguration()
//Set the `ControlledBy` level to the value stored in the logLevelCommand
.MinimumLevel.ControlledBy(logLevelCommand.LoggingLevelSwitch)
.WriteTo.WebloggerSink(
logger =>
{
//Register the command and now you can change the logging level.
logger.RegisterCommand(logLevelCommand);
})
.WriteTo.Console()
.CreateLogger();
You can also create customer Renders
that format the entire log message. The WebLoggerSink
accepts an IRenderMessage
interface as an optional parameter.
Two implmentation have been provided in the library for you. The renderer: new RenderSinkTextColor())
formats the time stamp and log level as colored text surrounded with "[]".
The default provided Renderer wraps the timestamp and log level inside a colored div and provides addtional padding. This is achived by using the renderer: new RenderSinkHtml())
implmentation.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink(renderer: new RenderSinkTextColor())
.CreateLogger();
Source Generators
A source generator analyzer project has now been created and is included in the solution. This is my first shot at these so be kind!
The project WebLogger.Generators
includes a source generator used to discover and create IWebLoggerCommands
. To get started using
the source generators add the WebLogger.Generators.nupkg
to your project.
Note: As of 1.1.3 Generators now use IIncrementalGenerator interface and only generate output when the the Argumnets change on you method attributes!
<ProjectReference Include="..\..\source\WebLogger.Generators\WebLogger.Generators.csproj" />
After adding the package the CommandHandlerAttribute
, CommandStore
, TargetCommand
, IStoredCommands
, and StoredCommandsExtensions
types will be generated and added to your project in the CommandHandlerAttribute.g.cs file.
// <auto-generated/>
namespace WebLogger.Generators
{
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false)]
public class CommandHandlerAttribute : global::System.Attribute
{
//...
}
}
Create a new partial class including one method with the ICommandHandler
method signature. Note that you can apply this attribute
to any method on your class however once the generator starts expands your class you will get compilation errors informing you the method doesn't match.
using WebLogger.Generators;
namespace WebLogger.ConsoleApp.GeneratedCommands
{
public partial class GeneratedCommand
{
[CommandHandler(
"Generated Command",
"A new class generating a custom command",
"This does nothing but prove something cool could happen.")]
public ICommandResponse ExecutedMethod(string command, List<string> args)
{
return CommandResponse.Success(command, "WOW, something cool just happened.");
}
}
}
The generated code behind the scenes will create partial class implmenting the IWebLoggerCommand
interface. All the defined attribute parameters will be
automatically married to the interfaces properties. See generated code:
namespace WebLogger.ConsoleApp.GeneratedCommands
{
public partial class GeneratedCommand : IWebLoggerCommand
{
public string Command => "GENERATEDCOMMAND";
public string Description => "A new class generating a custom command";
public string Help => "This does nothing but prove something cool could happen.";
public Func<string, List<string>, ICommandResponse> CommandHandler => ExecuteCommand;
protected ICommandResponse ExecuteCommand(string command, List<string> args)
{
try
{
return ExecutedMethod(this.Command, args);
}
catch (Exception e)
{
return CommandResponse.Error(this, e);
}
}
}
}
Your class can now be registered like any other command. Assuming there is a paremater-less constructor the command will even be automatically discovered with the extension methods listed above. If your class is far more complex you can instantiate it and register the instance with a WebLogger
logger.DiscoverCommands(Assembly.GetAssembly(typeof(Program)))
.DiscoverProvidedCommands();
// Or create a new instance and register.
logger.RegisterCommand(new GeneratedCommand());
You can also create a single class containing several CLI commands known as a CommandStore
. Create a partial class
marked with the CommandStore
attribute that includes at least one method marked with the TargetCommand
attribute. The source generator will created a new partial class extending your functionality.
This new partial class will include a private readonly List<IWebLoggerCommand> _commands
pointing the handlers at the methods you defined.
[CommandStore]
public partial class RoomControlCommandStore
{
[TargetCommand(
"PWRON",
"Powers on the room",
"Parameters: NA")]
public ICommandResponse PowerOnCommand(string command, List<string> args)
{
//Power on the room and return the results.
return CommandResponse.Success(command, "We did the thing worth doing");
}
[TargetCommand(
"PWROFF",
"Powers off the room",
"Parameters: NA")]
public ICommandResponse PowerOffCommand(string command, List<string> args)
{
//Power off the room and return the results.
throw new NotImplementedException("Room power off threw exception");
}
[TargetCommand(
"SOURCE",
"Selects the specified source in the room",
"Parameters: NA")]
public ICommandResponse SelectSourceCommand(string command, List<string> args)
{
return args.Count > 0
? CommandResponse.Success(command, $"Selected source: {args[0]}")
: CommandResponse.Failure(command, "Please provide a valid Source key argument.");
}
/// <summary>
/// Note, this method will not generate a command.
/// </summary>
public void TestInvalid()
{
}
}
The generator will implement the IStoredCommands
interface and you type can now be used to register commands with the weblogger.
During command registration your private list will be constructed and populated with values.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.WebloggerSink(
logger =>
{
//Use the generated logger extension method
logger.RegisterCommandStore(new RoomControlCommandStore());
//Or use the IStoredCommands interface extensions
var roomCli = new RoomControlCommandStore();
roomCli.RegisterCommands(logger);
})
.WriteTo.Console()
.CreateLogger();
Behind the scenes the follow partial class will created by the generator.
// <auto-generated/> @6/6/2023 7:59:59 PM
namespace WebLogger.ConsoleApp.GeneratedCommandStore
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("WebLogger", "1.1.4")]
partial class RoomControlCommandStore : global::WebLogger.Generators.IStoredCommands
{
//Code generated by reading the following:
//Namespace : namespace WebLogger.ConsoleApp.GeneratedCommandStore
//Name: RoomControlCommandStore
private List<IWebLoggerCommand> _commands;
private ICommandResponse PowerOnCommand_Generated(string command, List<string> args)
{
try
{
return PowerOnCommand(command, args);
}
catch (Exception e)
{
return CommandResponse.Error(command, e.Message);
}
}
private ICommandResponse PowerOffCommand_Generated(string command, List<string> args)
{
try
{
return PowerOffCommand(command, args);
}
catch (Exception e)
{
return CommandResponse.Error(command, e.Message);
}
}
public IEnumerable<IWebLoggerCommand> GetStoredCommands()
{
_commands = new List<IWebLoggerCommand>()
{
new WebLoggerCommand(PowerOnCommand_Generated, "PWRON","Powers on the room","Parameters: NA"),
new WebLoggerCommand(PowerOffCommand_Generated, "PWROFF","Powers off the room","Parameters: NA"),
};
return _commands;
}
}
}
There are still a few open issues that could really improve this.
- Attribute parameters must be string literals, using
nameof(Blah)
for example will throw an exception during generation.
The example console application includes a folder with (4) example generated commands.
HTML Rendering
Messages sent back to the web logger console can now render HTML templates. Using the HTML template classes in the WebLgger.Render
namespace
you can compose HTML markup that will be honored in the console output window of the webloger. See main image for example output. To get started create a HtmlElement
instance.
The HtmlElement
is public readonly ref struct HtmlElement
nad must be created with valid data.
HtmlElement div = new HtmlElement(Element.Div);
Several static factories are provided to created Markup that is ready to use.
var paragraph = HtmlElement.Paragraph("Welcome to the WebLogger HTML console application.");
var header = HtmlElement.H1Element("Welcome to the WebLogger HTML console application.");
Elements can be configured with an HtmlElementOptions
class. HtmlElementOptions
can be implicatly converted to a System.Drawing.Color
struc and colors can be passed into the element directly.
//Get a color back from the configured colors in the WebLogger Options.
var color = ColorsFactory.Instance.GetColor(Severity.Verbose)
var options = new HtmlElementOptions(additionalStyles: "background-color:red;", color: color);
var paragraph = HtmlElement.Paragraph("Welcome to the WebLogger HTML console application.", options);
var paragraphWithColor = HtmlElement.Paragraph("Welcome to the WebLogger HTML console application.", color);
Elements can be Appended and Inserterd into one another using the Apend()
and Insert()
method. Using these method you can put it all together and build markup. The example below is from the WebLogger welcome.
var styles = new StringBuilder("text-align:center;padding:10px;font-weight:bold;border: 3px solid ")
.RenderColor(ColorFactory.Instance.WarningColor)
.Append(";");
var options = new HtmlElementOptions(additionalStyles: styles.ToString());
var welcome = HtmlElement.H1Element("WEBLOGGER CONSOLE", ColorFactory.Instance.WarningColor)
.AppendLine()
.Append(HtmlElement.Paragraph("Welcome to the WebLogger HTML console application."))
.Append(HtmlElement.Paragraph(
"Enter console commands into the input field below. Type help at anytime to display all available commands"));
SendSerial(HtmlElement.Div(welcome, options: options).Render());
Note the final line calls the Render()
method. This method converts the build up elements into a sting. Another example from the WebLoggerSink
renders exceptions in a red box. Note the use of a string builder is only required to provide custom styles.
public static string RenderError(LogEvent logEvent, IFormatProvider formatProvider)
{
var color = ColorFactory.Instance.GetColor(Severity.Error);
if (logEvent.Exception != null)
{
return CreatePrefix(logEvent, color)
.Append(logEvent.RenderMessage(formatProvider))
.Append(HtmlElement.Span(", Exception: "))
.Append(RenderExceptions(logEvent.Exception, color))
.Render();
}
return CreatePrefix(logEvent, color)
.Append(HtmlElement.Span(logEvent.RenderMessage(formatProvider), color))
.Render();
}
private static HtmlElement RenderExceptions(Exception exception, Color color)
{
var builder = new StringBuilder("background-color:")
.RenderColor(Color.DarkRed)
.Append(";border: 3px solid rgba(255,0,0,1);");
return HtmlElement.Table(
HtmlElement.TableRow(
HtmlElement.TableData(exception.ToString(), new HtmlElementOptions(additionalStyles: builder.ToString()))
));
}
Looking forward to seeing what complex markup your able to create with these builder functions.
Release Notes
Version 1.1.7
- Queried assembly versions and info.text file will now be compared and will be used to keep the webpage in sync with the running version.
Version 1.1.5
- Created HTML Renders
- Moved Source Generator static files to WebLogger project to resolve issues with multiple projects in a single solution
- Serilog sink is now formatting HTML messages.
- WebLogger Options now includes a colors factory.
Version 1.1.4
- Created command store generator.
- Created
ControlledBy
command in the serilog sink to updated verbosity levels at runtime.
Version 1.1.3
- Moved to inremental source generator
- Source generator now creates the CommandHandleAttribute as such this is no longer included in the WebLogger assembly.
- Source generator now handles top level namespace declarations.
Version 1.1.2
- Created source generator library to automatically create web logger commands
- Created Crestron SIMPL windows support. There are now two SPLUS modules used for the server and commands.
- Commands can now be executed with a partial match and properly reject emty strings
Version 1.1.0
- Changed command handler from
Action<string, List<string>
toFunc<string, List<string>, CommandResponse
to provide a command response. Returned string will now be Written to the webLogger output. - Created
WebLogger.Serilog
Project and Nuget Package. This allowed the web logger to remove the dependency on Serilog. Weblogger.Serilog now provides logger configuration extensions and SerilogSink for the weblogger server. - Provided
Action<IWebLogger>
to the WebLoggerFactory - Created extension methods to discovery all defined commands in a provided assembly.
- Commands are now partially matched and will execute the first found command. i.e. type
up
to executeupdate
Version 1.0.1
- Initial Release
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Framework | net472 is compatible. net48 was computed. net481 was computed. |
-
.NETFramework 4.7.2
- Crestron.SimplSharp.SDK.Library (>= 2.20.66)
- SimplSharp.Library.Targets (>= 0.1.9)
- WebLogger (>= 1.3.0)
-
net6.0
- Crestron.SimplSharp.SDK.Library (>= 2.20.66)
- SimplSharp.Library.Targets (>= 0.1.9)
- WebLogger (>= 1.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.3.0 | 96 | 7/18/2024 |
1.3.0-alpha.5 | 47 | 7/15/2024 |
1.2.0 | 78 | 7/14/2024 |
1.2.0-alpha.53 | 46 | 7/14/2024 |
1.2.0-alpha.51 | 47 | 7/14/2024 |
1.2.0-alpha.49 | 47 | 7/14/2024 |
1.2.0-alpha.46 | 47 | 7/14/2024 |
1.1.10 | 116 | 3/18/2024 |
1.1.9.1 | 103 | 3/18/2024 |
1.1.9 | 150 | 3/15/2024 |
1.1.8 | 185 | 3/15/2024 |
1.1.7 | 179 | 6/11/2023 |
1.1.5 | 150 | 6/9/2023 |
1.1.3 | 140 | 6/5/2023 |
1.1.2 | 139 | 6/3/2023 |
1.1.0 | 138 | 6/1/2023 |
1.0.1 | 147 | 5/26/2023 |
1.0.0 | 145 | 5/26/2023 |