I'm working with the NuGet Command Line Parser Library. I want to be able to set up some command line tools and I want the command(-v or --version) to return the current version of the application. I have another method set up to find the version and set it to a string so all I need now is that command line argument to set to that current version rather than just expecting something after the command. thanks for the help!
static string GetVersion() {
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
string currentVersion = fvi.FileVersion;
return currentVersion;
}
class Options
{
[Option('v', "version", HelpText = "Sets version to be run")]
public string Version { get; set; }
}
that's just the important parts.
Based on the documentation it looks like you want something like this:
// Define a class to receive parsed values
class Options {
[Option('v', "version",
HelpText = "Prints version information to standard output.")]
public bool Version { get; set; }
[ParserState]
public IParserState LastParserState { get; set; }
[HelpOption]
public string GetUsage() {
return HelpText.AutoBuild(this,
(HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
}
}
// Consume them
static void Main(string[] args) {
var options = new Options();
if (CommandLine.Parser.Default.ParseArguments(args, options)) {
// Values are available here
if (options.Version) Console.WriteLine("Version: {0}", GetVersion());
}
}
You don't need the Version property to get the version - you can just use it as a "switch" to tell the program to display the version. If you wanted the user to set the version then a get/set string property would be more appropriate.
Related
I am exploring Function App running on .net5 in the new isolated mode. I have HTTP triggered functions that I want to advertise via OpenAPI / Swagger.
To do so, I am using the package Microsoft.Azure.WebJobs.Extensions.OpenApi in preview (0.7.2) to add the OpenAPI functionality to my Function App.
I am trying to have the enums to be shown as string in the OpenAPI page but I can't have it working properly.
Here is the setup in the Program.cs file:
public static class Program
{
private static Task Main(string[] args)
{
IHost host = new HostBuilder()
.ConfigureAppConfiguration(configurationBuilder =>
{
configurationBuilder.AddCommandLine(args);
})
.ConfigureFunctionsWorkerDefaults(builder =>
{
builder.Services.Configure<JsonSerializerOptions>(options =>
{
options.Converters.Add(new JsonStringEnumConverter());
options.PropertyNameCaseInsensitive = true;
});
})
.ConfigureServices(services =>
{
// Registers any services.
})
.Build();
return host.RunAsync();
}
}
Here is the enum:
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ApprovalContract
{
[EnumMember(Value = "Approved")]
Approved = 1,
[EnumMember(Value = "Rejected")]
Rejected = 2
}
And one of the class that uses it:
public sealed class DeletionResponseContract
{
[JsonPropertyName("approval")]
public ApprovalContract Approval { get; set; }
}
I replaced any references to Newtonsoft.Json by System.Text.Json everywhere.
Here is the output in the Swagger page:
Question
How can I serialize enum as string instead of int in the Swagger page with an HTTP triggered Azure Function running on .net5?
Update
I saw that the JsonStringEnumConverter's constructor gives the indication to allow integer values:
public JsonStringEnumConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
{
this._namingPolicy = namingPolicy;
this._converterOptions = allowIntegerValues ? EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers : EnumConverterOptions.AllowStrings;
}
I modified my configuration like this, without any success:
builder.Services.Configure<JsonSerializerOptions>(options =>
{
options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false));
options.PropertyNameCaseInsensitive = true;
});
You must implemente ISchemaFilter and set it on AddSwaggerGen. It will generate a better description of your enum items.
builder.Services.AddSwaggerGen(c =>
{
c.SchemaFilter<EnumSchemaFilter>();
});
//your implementation
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
model.Enum.Clear();
Enum.GetNames(context.Type)
.ToList()
.ForEach(name => model.Enum.Add(new OpenApiString($"{Convert.ToInt64(Enum.Parse(context.Type, name))} - {name}")));
}
}
}
I was inspired by Murilo's answer, but couldn't get it to work. So here is an alternative solution:
Create a document filter that will:
find all the enum properties in you swagger schemas
then find the matching property in your code using reflection (note: this doesn't take account of namespaces, so if you have multiple classes with the same name it could fail)
update the swagger property with the values from the c# enum
public class EnumDocumentFilter : IDocumentFilter
{
public void Apply(IHttpRequestDataObject req, OpenApiDocument document)
{
foreach(var schema in document.Components.Schemas)
foreach(var property in schema.Value.Properties)
if (property.Value.Enum.Any())
{
var schemaType = Assembly.GetExecutingAssembly().GetTypes().Single(t => t.Name == Camel(schema.Key));
var propertyType = schemaType.GetProperty(Camel(property.Key)).PropertyType;
property.Value.Enum = Enum.GetNames(propertyType)
.Select(name => new OpenApiString(name))
.Cast<IOpenApiAny>()
.ToList();
property.Value.Type = "string";
property.Value.Default = property.Value.Enum.First();
property.Value.Format = null;
}
}
private static string Camel(string key)
=> $"{char.ToUpperInvariant(key[0])}{key[1..]}";
}
Then register that filter in your OpenApiConfigurationOptions
public class OpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions
{
...
public override List<IDocumentFilter> DocumentFilters { get => base.DocumentFilters.Append(new EnumDocumentFilter()).ToList(); }
}
Thanks Alan Hinton for your answer, I was able to get the custom document filter working using reflection.
The problem:
The enums were auto generated and I cannot keep adding StringEnumCovertor attribute each time the code was refreshed.
Auto-generated code:
public enum Status
{
[System.Runtime.Serialization.EnumMember(Value = #"new")]
New = 0,
[System.Runtime.Serialization.EnumMember(Value = #"confirmed")]
Confirmed = 1,
[System.Runtime.Serialization.EnumMember(Value = #"processing")]
Processing = 2
}
public partial class Order
{
/// <summary>Status</summary>
[Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public Status Status { get; set; }
}
Solution:
public OpenApiConfigurationOptions()
{
DocumentFilters.Add(new OpenApiEnumAsStringsDocumentFilter());
}
public class OpenApiEnumAsStringsDocumentFilter : IDocumentFilter
{
private const string YourNamespace = "your.namespace";
private const string EnumDefaultMemberValue = "value__";
private const string StringSchemaType = "string";
public void Apply(IHttpRequestDataObject request, OpenApiDocument document)
{
var assemblyTypes = Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(x => !string.IsNullOrEmpty(x.FullName) && x.FullName.StartsWith(YourNamespace, StringComparison.InvariantCulture));
// Loop all DTO classes
foreach (var schema in document.Components.Schemas)
{
foreach (var property in schema.Value.Properties)
{
if (property.Value.Enum.Any())
{
var schemaType = assemblyTypes.SingleOrDefault(t => t.Name.Equals(schema.Key, StringComparison.InvariantCultureIgnoreCase));
if (schemaType == null)
continue;
var enumType = schemaType.GetProperty(string.Concat(property.Key[0].ToString().ToUpper(), property.Key.AsSpan(1))).PropertyType;
UpdateEnumValuesAsString(property.Value, enumType);
}
}
}
// Loop all request parameters
foreach (var path in document.Paths)
{
foreach (var operation in path.Value.Operations)
{
foreach (var parameter in operation.Value.Parameters)
{
if (parameter.Schema.Enum.Any())
{
var enumType = assemblyTypes.SingleOrDefault(t => t.Name.Equals(parameter.Name, StringComparison.InvariantCultureIgnoreCase));
if (enumType == null)
continue;
UpdateEnumValuesAsString(parameter.Schema, enumType);
}
}
}
}
}
private static void UpdateEnumValuesAsString(OpenApiSchema schema, Type enumType)
{
schema.Enum.Clear();
enumType
.GetTypeInfo()
.DeclaredMembers
.Where(m => !m.Name.Equals(EnumDefaultMemberValue, StringComparison.InvariantCulture))
.ToList()
.ForEach(m =>
{
var attribute = m.GetCustomAttribute<EnumMemberAttribute>(false);
schema.Enum.Add(new OpenApiString(attribute.Value));
});
schema.Type = StringSchemaType;
schema.Default = schema.Enum.FirstOrDefault();
schema.Format = null;
}
}
According to the Microsoft.Azure.WebJobs.Extensions.OpenApi.Core documentation you should be able to set the [JsonConverter(typeof(StringEnumConverter))] (with the Newtonsoft package) attribute on the enum to trigger the usage of strings in the Swagger.
I had issues however that the OpenAPI document still didn't show the enum as strings, and I believe the issue is related some compatibility between Newtonsoft version 13.0.1 (which is dependency for my project) and Azure Function Core Tools (AFCT) v. 3.41. It is anyway solved when either downgrading Newtonsoft to 12.0.3 or lower OR upgrading the project to use Azure Functions V4 and thus also Azure Function Core Tools v. 4.x.x.
The reason I suspect the Azure Function Core Tools to be the cause and not something else related to the Azure Function version, is that AFCT loads Newtonsoft assembly 12.0.0.0 when you start it, and if you're using Newtonsoft 12.0.3 in the project, the same assembly may be used by Microsoft.Azure.WebJobs.Extensions.OpenApi.Core. But if the project uses 13.0.1, it refers to assembly version 13.0.0.0, which is loaded by Microsoft.Azure.WebJobs.Extensions.OpenApi.Core alongside the 12.0.0.0 assembly. This mismatch in versions could be why the attribute isn't working as expected.
I have added CommandLineParser library into my project and I have configure all the arguments which should be provided to my project for to support silent installation of the same.
An InstallOptions class is being created with some "Option" attributes for each of the required and non-required arguments to the same e.g. below
public class InstallOptions
{
[Option("path", Required = true, HelpText = "The installation path where you want the application installed.")]
public string InstallPath { get; set; }
[Option("dbname", Required = true, HelpText = "Database name.")]
public string DbName { get; set; }
[Option("dbserver", Required = true, HelpText = "Database server name or IP address.")]
public string DbServer { get; set; }
[HelpOption]
public string DisplayHelpOnParseError()
{
var help = new HelpText()
{
AddDashesToOption = true
};
var errors = "";
if (LastParserState.Errors.Any())
{
errors = help.RenderParsingErrorsText(this, 0);
}
//HACK fix where if help.addoptions is called more than once it truncates the output
if (_help == null)
{
help.AddOptions(this);
_help = help;
}
else
{
return String.IsNullOrEmpty(errors) ? _help : "ERROR(S):" + errors + _help;
}
return help;
}
}
From my program.cs file I want to debug I am running my project as below
public static void Main(string[] args)
{
args = new string[3];
args[0] = "--path C:\\Program files\MyProject";
args[1] = "--dbname myDBName";
args[2] = "--dbserver myDBServer";
var result = Parser.Default.ParseArguments(args, installOptions);
if (!result) throw new ArgumentException(installOptions.DisplayHelpOnParseError());
}
in the above code I all the time getting result = false and states throws below error message
--path required. The installation path where you want the application installed.
--dbname required. Database name.
--dbserver required. Database server name or IP address.
Please help me how to pass all 3 parameter to my project to test it is working correctly.
Thanks in advance
Arguments should be passed as below
Im working on a console tool which accepts some arguments and then parses to the Option class.
What I need is a property that will verify if only one of many marked fields in Option class has values (arguments were delivered).
Ex.
Its ok when we run:
my.exe -a
my.exe -b
but NOT:
my.exe
my.exe -a -b
CommandLine.OptionAttribute cannot do such a thing. What i did is:
Main class args[] got extension .Parse:
args.Parse(options)` //where options is my Options class
inside:
CommandLine.Parser.Default.ParseArguments(args, options);
var isOnlyOneOfMany = options.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(OneOfMany)) && prop.GetValue(options) != null).Count() == 1;
how to do this better way?
I will rewrite your Options class
class Options
{
public int OneOfManyCount { get; private set;}
// This is not OneOfMany
[Option('n', "name", Required = true)]
public string Name { get; set; }
private string _Value;
[OneOfMany]
[Option('v', "value", Required = true)]
public string Value { get { return _Value; } set { _Value = value; OneOfManyCount++;} }
private string _Date;
[OneOfMany]
[Option('d', "data", Required = true)]
public string Data { get { return _Date; } set { _Date = value; OneOfManyCount++;} }
}
and in your main, you can call options.OneOfManyCount to get the number of arguments
CommandLine.Parser.Default.ParseArguments(args, options);
if(options.OneOfManyCount != 1) //Do something
And please notice if you have a DefaultValue attribute on one of you OneOfMany, it will hit the set one more time which means OneOfManyCount will have unexpected value.
Update: This solution doesn't work from version 2.8.0 because both SetName and Group are not allowed in option
You can use SetName and GroupName to achieve this behavior:
GroupName: Available from version 2.7+
An option group represents a group of options which are optional, but at least one should be available.
SetName: Available from version 1.9+
It means that you can run commands of one set at a time. You can't mix
commands of more than one set otherwise you get an error.
The new option class:
public class Options
{
[Option('a', "aValue", Group = "Values", SetName = "ASet")]
public bool AValue { get; set; }
[Option('b', "aValue", Group = "Values", SetName = "BSet")]
public bool BValue { get; set; }
}
How to parse the args:
var options = Parser.Default.ParseArguments<Options>(args);
I am trying to create a new QulaificationType for which the workers have to answer a question to gain the qualification.Below is my C# code. I am getting an error while using createQualificationType method in C# api. Please help.
using System;
using System.Collections.Generic;
using System.Text;
using Amazon.WebServices.MechanicalTurk;
using Amazon.WebServices.MechanicalTurk.Domain;
namespace CreateHITExample
{
class Program
{
static SimpleClient client = new SimpleClient();
static void Main(string[] args)
{
CreateNewHIT();
}
static void CreateNewHIT()
{
**QuestionFormQuestion** question = new QuestionFormQuestion();
question.IsRequired = true;
question.QuestionIdentifier = "1";
**ContentType qnContent = new ContentType();**
QualificationType qualType = client.CreateQualificationType("MyQual2", string.Empty, "My Qualification Type", QualificationTypeStatus.Active, 0, **question**, "680", 600, true, 100);
string qualTypeId = qualType.QualificationTypeId;
Console.WriteLine("Created Qualification Type ID 2: {0}", qualTypeId);
}
}
}
I have to pass the question object as the parameter to CreateQualificationType method.
As you can see from the above piece of code, question object is of class QuestionFormQuestion.
Below are the class definitions that might be of some help.
QuestionFormQuestion Class definition from AWS MTurk dotnet API:
public class QuestionFormQuestion
{
public QuestionFormQuestion();
public AnswerSpecificationType AnswerSpecification { get; set; }
public string DisplayName { get; set; }
public bool IsRequired { get; set; }
[XmlIgnore]
public bool IsRequiredSpecified { get; set; }
**public ContentType QuestionContent { get; set; }**
public string QuestionIdentifier { get; set; }
}
The actual question text goes into the QuestionContent attribute, which is of type "ContentType".
ContentType Class definition from AWS MTurk dotnet API:
public class ContentType
{
public ContentType();
[XmlChoiceIdentifier("ItemsElementName")]
[XmlElement("Application", typeof(ApplicationContentType))]
[XmlElement("Binary", typeof(BinaryContentType))]
[XmlElement("FormattedContent", typeof(String))]
[XmlElement("Text", typeof(String))]
[XmlElement("Title", typeof(String))]
public object[] Items { get; set; }
[XmlElement("ItemsElementName")]
[XmlIgnore]
public ItemsChoiceType[] ItemsElementName { get; set; }
}
I have to move the Actual Question sentence to the [XmlElement("Text", typeof(String))] element of the ContentType object. I dont know the syntax to do that. Please Help.
I was running into the same ValueException error message using the Ruby SDK until I discovered that (unlike ALL the API documentation examples, which showed XML was expected) CreateHit was expecting a Hash, not XML for some parameters (XML for Question, but Hash for QualificationRequirement for example).
In my case it was rejecting a QualificationRequirement when I supplied it XML like they show in the docs, but it worked when I provided it as a Hash.
But since the error message is the same, I suspect that may be what you are running into as well. (The error is language independent... it's not form your SDK, it's what is being returned from AWS when your SDK submits the HIT.)
# does NOT work:
usa_qualification = 'XML STRING LIKE AWS DOCS GIVE'
# DOES work:
usa_qualification = {
:QualificationTypeId => "00000000000000000071",
:Comparator => "EqualTo",
:LocaleValue => { :Country => "US"},
result = mturk.createHIT( :Title => title,
...
:QualificationRequirement => usa_qualification ,
...
I'm trying to write a function in C# that takes in a string containing typescript code and returns a string containing JavaScript code. Is there a library function for this?
You can use Process to invoke the compiler, specify --out file.js to a temporary folder and read the contents of the compiled file.
I made a little app to do that:
Usage
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
To get the JS string
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
Full source with example and comments:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
// compiles a TS file
TypeScriptCompiler.Compile(#"C:\tmp\test.ts");
// if no errors were found, read the contents of the compile file
string javascriptSource = File.ReadAllText(#"C:\tmp\test.js");
}
catch (InvalidTypeScriptFileException ex)
{
// there was a compiler error, show the compiler output
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
public static class TypeScriptCompiler
{
// helper class to add parameters to the compiler
public class Options
{
private static Options #default;
public static Options Default
{
get
{
if (#default == null)
#default = new Options();
return #default;
}
}
public enum Version
{
ES5,
ES3,
}
public bool EmitComments { get; set; }
public bool GenerateDeclaration { get; set; }
public bool GenerateSourceMaps { get; set; }
public string OutPath { get; set; }
public Version TargetVersion { get; set; }
public Options() { }
public Options(bool emitComments = false
, bool generateDeclaration = false
, bool generateSourceMaps = false
, string outPath = null
, Version targetVersion = Version.ES5)
{
EmitComments = emitComments;
GenerateDeclaration = generateDeclaration;
GenerateSourceMaps = generateSourceMaps;
OutPath = outPath;
TargetVersion = targetVersion;
}
}
public static void Compile(string tsPath, Options options = null)
{
if (options == null)
options = Options.Default;
var d = new Dictionary<string,string>();
if (options.EmitComments)
d.Add("-c", null);
if (options.GenerateDeclaration)
d.Add("-d", null);
if (options.GenerateSourceMaps)
d.Add("--sourcemap", null);
if (!String.IsNullOrEmpty(options.OutPath))
d.Add("--out", options.OutPath);
d.Add("--target", options.TargetVersion.ToString());
// this will invoke `tsc` passing the TS path and other
// parameters defined in Options parameter
Process p = new Process();
ProcessStartInfo psi = new ProcessStartInfo("tsc", tsPath + " " + String.Join(" ", d.Select(o => o.Key + " " + o.Value)));
// run without showing console windows
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
// redirects the compiler error output, so we can read
// and display errors if any
psi.RedirectStandardError = true;
p.StartInfo = psi;
p.Start();
// reads the error output
var msg = p.StandardError.ReadToEnd();
// make sure it finished executing before proceeding
p.WaitForExit();
// if there were errors, throw an exception
if (!String.IsNullOrEmpty(msg))
throw new InvalidTypeScriptFileException(msg);
}
}
public class InvalidTypeScriptFileException : Exception
{
public InvalidTypeScriptFileException() : base()
{
}
public InvalidTypeScriptFileException(string message) : base(message)
{
}
}
}
Perhaps you could use a JavaScript interpreter like JavaScriptDotNet to run the typescript compiler tsc.js from C#.
Something like:
string tscJs = File.ReadAllText("tsc.js");
using (var context = new JavascriptContext())
{
// Some trivial typescript:
var typescriptSource = "window.alert('hello world!');";
context.SetParameter("typescriptSource", typescriptSource);
context.SetParameter("result", "");
// Build some js to execute:
string script = tscJs + #"
result = TypeScript.compile(""typescriptSource"")";
// Execute the js
context.Run(script);
// Retrieve the result (which should be the compiled JS)
var js = context.GetParameter("result");
Assert.AreEqual(typescriptSource, js);
}
Obviously that code would need some serious work. If this did turn out to be feasible, I'd certainly be interested in the result.
You'd also probably want to modify tsc so that it could operate on strings in memory rather than requiring file IO.
The TypeScript compiler file officially runs on either node.js or Windows Script Host - it is written in TypeScript itself (and transpiled to JavaScript). It requires a script host that can access the file system.
So essentially, you can run TypeScript from any language as long as you can wrap it in a script engine that supports the file system operations required.
If you wanted to compile TypeScript to JavaScript purely in C#, you would end up writing a C# clone of the compiler.