How to retrieve the args array after using AddCommandLine - .net-core

I am working on a POC for a console application and I am struggling to retrieve the command line values from the configuration after using AddCommandLine in the set up.
csproj
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
Program class
public static class Program
{
public static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.RollingFile("Logs//log.txt")
.CreateLogger();
await CreateHostBuilder(args)
.Build()
.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("settings.json", true, true);
config.AddCommandLine(args);
})
.ConfigureServices((hostcontext, services) =>
{
services.AddHostedService<ConsoleApp>();
});
}
ConsoleApp class
public class ConsoleApp : IHostedService
{
private readonly IConfiguration config;
private readonly ILogger<ConsoleApp> log;
public ConsoleApp(IConfiguration configuration, ILogger<ConsoleApp> logger)
{
config = configuration;
log = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
var t = config.GetSection("Args");
Parser.Default.ParseArguments<DeleteOptions>(t)
.WithParsed<DeleteOptions>()
.WithNotParsed();
foreach (var c in config.AsEnumerable())
{
log.LogInformation($"{c.Key, -15}:{c.Value}");
}
log.LogInformation($"Completing Start Task");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
log.LogInformation($"Complete End Task");
return Task.CompletedTask;
}
}
The Parser section before the foreach loop does not compile and the output from the loop does not print out any of the arguments I have added.
I am aware of the general advice that var someValue = Configuration.GetValue<int>("MySetting:SomeValue"); where the argument is --MySetting=SomeValue is the recommended way to retrieve cmd line values.
The values I am using as parameters are delete -e CI -t depchpolestar -l de-DE and when I look at my config object I see
which is why I think the line var t = config.GetSection("Args"); should retrieve the args array. I have also tried var t = config.GetValue<string[]>("Args"); but neither seems to work. It appears to me that index 4 of the configuration object is a string array keyed by "Args"
How do I retrieve the string array so I can pass it into CommandLineParser's ParseArguments method?
[Edit] One Solution:
I can now get the args passed through but it is not a particularly nice approach; If I construct the argument as --delete "-e CI -t depchpolestar -l de-DE" instead of delete -e CI -t depchpolestar -l de-DE and adding the following code to the ConsoleApp class:
var args = config.GetValue<string>("delete");
string[] arguments = null;
if(!string.IsNullOrEmpty(args))
{
var tempArgs = args.Split(" ");
arguments = new string[tempArgs.Length + 1];
arguments[0] = "delete";
for(int i = 0; i < tempArgs.Length; ++i)
{
arguments[i + 1] = tempArgs[i];
}
}
Parser.Default.ParseArguments<DeleteOptions>(arguments)
.WithParsed<DeleteOptions>(async c => await c.Dowork())
.WithNotParsed(HandleParseError);
execution hits the DoWork method. Good but DeleteOptions.cs defines a Verb and the intention is to add more commands. So more work to do but going the right way.
[Edit] I have also realised that I do not need to add the AddCommandLine() call as they are added by default.

ok it seemed that I over complicated this and ended up with this:
public static class Program
{
public static async Task Main(string[] args)
{
var builtHost = CreateHostBuilder(args).Build();
var console = builtHost.Services.GetService<ConsoleApp>();
await console.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("settings.json", true, true);
config.AddCommandLine(args);
})
.ConfigureServices((hostcontext, services) =>
{
services.AddTransient<ConsoleApp>();
});
}
and this as the run method in ConsoleApp:
public Task Run()
{
while (true)
{
var input = ReadFromConsole();
if (string.IsNullOrWhiteSpace(input))
{
continue;
}
else if (input.ToLower().Equals("exit"))
{
break;
}
else
{
Parser.Default.ParseArguments<DeleteOptions, ConcatOptions, DownloadOptions, ReportOptions>(input.Split(" "))
.WithParsed<DeleteOptions>(async options => await options.DoWork())
.WithParsed<ConcatOptions>(async options => await options.DoWork())
.WithParsed<DownloadOptions>(async options => await options.DoWork())
.WithParsed<ReportOptions>(async options => await options.DoWork())
.WithNotParsed(HandleParseError);
}
}
return Task.CompletedTask;
}
This works fine for allowing me to use it as an interactive console app. I do have a problem with the DI though. I have created an OptionsBase class which set up logging and I have done it this way as trying to add a parameter to any of the Options classes fails stating unable to find the parameterless constructor. So I am assuming that CommandLine requires default constructors to work. Gett ing a logger the way I have gives me multiple log files so I need to fix that.

Related

NPoco exhausting connection pool on .NET Core 3.1

When I call into my repository 10000 times, it either takes minutes (for a very simple keyed query, which does not take minutes to do on the database itself), or dies quickly with a connection pool exhaustion message. I know I am doing something wrong with some combination of disposing objects, creating objects, DI container lifespans, and so on. What am I doing wrong? I have tried a few permutations of .Singleton / .Scoped, a ThreadLocal cache of databases, etc.
Code is executing on Windows 10, framework is .NET Standard 2.1 (running on .NET Core 3.1), talking to SQL Server 2016.
My registration policy (Lamar):
public NPocoRegistry()
{
For<IDatabase>()
.Use(ctx => ctx.GetInstance<DatabaseFactory>().GetDatabase())
.Scoped();
For<DatabaseFactory>().Use(ctx =>
{
var configuration = ctx.GetInstance<IConfiguration>();
Database CreateDatabase()
{
return new Database(configuration.GetConnectionString("EdgeDev"),
DatabaseType.SqlServer2012,
SqlClientFactory.Instance)
{
KeepConnectionAlive = true
};
}
var configs = FluentMappingConfiguration.Configure(ctx.GetAllInstances<IMap>().ToArray());
return DatabaseFactory.Config(cfg => cfg
.UsingDatabase(CreateDatabase)
.WithFluentConfig(configs)
.WithMapper(new BooleanMapper())
.WithMapper(new BinaryStringMapper()));
}).Singleton();
Scan(scan =>
{
scan.TheCallingAssembly();
scan.AddAllTypesOf<IMap>();
});
}
My base repository:
public abstract class BaseNPocoRepository<T>
{
private readonly DatabaseFactory _dbFactory;
private readonly ThreadLocal<IDatabase> _databaseLocal;
protected BaseNPocoRepository(DatabaseFactory dbFactory)
{
_dbFactory = dbFactory;
_databaseLocal = new ThreadLocal<IDatabase>(_dbFactory.GetDatabase);
}
protected virtual IDatabase GetDatabase() => _databaseLocal.Value;
public virtual async Task CreateAsync(T item)
{
using var database = GetDatabase();
await database
.InsertAsync(item)
.ConfigureAwait(false);
}
public virtual async Task UpdateAsync(T item)
{
using var database = GetDatabase();
await database
.UpdateAsync(item)
.ConfigureAwait(false);
}
public virtual async Task DeleteAsync(T item)
{
using var database = GetDatabase();
await database
.DeleteAsync(item)
.ConfigureAwait(false);
}
public virtual async Task<IEnumerable<T>> RetrieveManyAsync()
{
using var database = GetDatabase();
return await database
.Query<T>()
.ToEnumerableAsync()
.ConfigureAwait(false);
}
}
A sample repository utilizing this pattern:
public class T_AccountRepository : BaseNPocoRepository<T_Account>
, IRetrieveMany<T_Account>
, IRetrieve<AccountId, T_Account>
{
public T_AccountRepository(DatabaseFactory dbFactory) : base(dbFactory)
{
}
public async Task<T_Account> RetrieveAsync(AccountId input)
{
using var database = GetDatabase();
return await database.Query<T_Account>()
.SingleAsync(x => x.AccountId == (int) input)
.ConfigureAwait(false);
}
}
How it's actually being called:
static async Task Main(string[] args)
{
Console.WriteLine("Booting up . . .");
var container = new Container(cfg =>
{
cfg.Scan(scan =>
{
scan.AssembliesFromApplicationBaseDirectory();
scan.AssemblyContainingType<NPocoRegistry>();
scan.LookForRegistries();
scan.With(new AllInterfacesConvention());
});
});
Console.WriteLine("Getting repository . . . ");
var repo = container.GetInstance<AccountRepository>();
Console.WriteLine("Starting benchmark . . .");
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
{
await repo.RetrieveAsync(1253832471);
}
Console.WriteLine(sw.ElapsedMilliseconds + "ms");
}

Upgrading AutoMapper from v6 to v9 and unit testing with Resolution Context

I'm hoping that somebody can help me. We are currently upgrading AutoMapper from v6 to v9 - we would go to v10 but the inability to create new ResolutionContext impacts our unit testing. That said with v9 we are still having the following issue with unit testing AutoMapper Converters...
A ConverterClass:
public class FooBarConverter :
ITypeConverter<FooSourceObject, BarDestinationObject>
{
/// <inheritdoc/>
public virtual BarDestinationObjectConvert(FooSourceObjectsource, BarDestinationObjectdestination, ResolutionContext context)
{
EnsureArg.IsNotNull(source, nameof(source));
switch (source.Type)
{
case SomeType.None:
return context.Mapper.Map<NoneBarDestinationObject>(source);
case SomeType.FixedAmount:
return context.Mapper.Map<FixedBarDestinationObject>(source);
case SomeType.Percentage:
return context.Mapper.Map<PercentageBarDestinationObject>(source);
default:
throw new ArgumentOutOfRangeException(nameof(source));
}
}
Before in AutoMapper 6 we had the following Unit Test (using Xunit):
public class FooBarConverterTests
{
private readonly FooBarConverter target;
private readonly Mock<IRuntimeMapper> mockMapper;
private readonly ResolutionContext resolutionContext;
public FooBarConverterTests()
{
this.mockMapper = new Mock<IRuntimeMapper>();
this.resolutionContext = new ResolutionContext(null, this.mockMapper.Object);
this.target = new FooBarConverter();
}
[Fact]
public void FixedAmountFooModel_ConvertsTo_FixedBarDomainModel()
{
// Arrange
var input = new Foo
{
Type = SomeType.FixedAmount
};
var expected = new DomainModels.FixedBarDestinationObject();
this.mockMapper.Setup(x => x.Map<FixedBarDestinationObject>(It.IsAny<FooSourceObjectsource>()))
.Returns(expected);
// Act
var actual = this.target.Convert(input, It.IsAny<BarDestinationObjectdestination>(), this.resolutionContext);
// Assert
actual.ShouldSatisfyAllConditions(
() => actual.ShouldNotBeNull(),
() => actual.ShouldBeAssignableTo<DomainModels.FixedBarDestinationObject>());
this.mockMapper.Verify(x => x.Map<DomainModels.FixedBarDestinationObject>(input));
}
}
Essentially, this was working fine, however since upgrading to v9, the mapping setup goes missing as it's passed through the resolution context. Meaning that the resulting call of Mapper.Map<FixedBarDestinationObject>() always returns null.
I understand that the ResolutionContext may have changed slightly, but I don't understand how to resolve this issue and ensure that the mock mapping is passed through to the underlying converter.
Thank you for any help or advice.
Thanks to Lucian I finally got my head around this:
public class FooBarConverterTests
{
private readonly FooBarConverter target;
private readonly IMapper mapper;
public FooBarConverterTests()
{
this.mapper = this.GetMapperConfiguration().CreateMapper();
this.target = new FooBarConverter();
}
[Fact]
public void FixedAmountFooModel_ConvertsTo_FixedBarDomainModel()
{
// Arrange
var input = new Foo
{
Type = SomeType.FixedAmount
};
var expected = new DomainModels.FixedBarDestinationObject();
// Act
var actual = this.Mapper.Map<BarDestinationObjectdestination>(input);
// Assert
actual.ShouldSatisfyAllConditions(
() => actual.ShouldNotBeNull(),
() => actual.ShouldBeAssignableTo<DomainModels.FixedBarDestinationObject>());
}
private MapperConfiguration GetMapperConfiguration()
{
return new MapperConfiguration(opt =>
{
opt.AddProfile<CustomAutomapperProfile>();
opt.ConstructServicesUsing(t =>
{
if (t == typeof(FooBarConverter))
{
return this.target;
}
return null;
});
});
}
}
So, I'm loading the mapper profile (which requires the converter) and call the converter through that, this ensures that the mapper profile is loaded.
As a bonus, this also means that I entirely do away with newing up the ResolutionContext and paves the way for upgrading to v10.

picocli : parse arguments without boiler plate

I generally store away all the command line options to a different class, say, CliArguments. This avoids the noise in the main class. This is what I have with picocli:
public final class MyApp {
private static final CliArguments cliArgs = new CliArguments();
private MyApp() {}
public static void main (String[] args) {
if (parseArgs (args)) {
new MyApp().execute();
}
}
/* want to avoid this boiler plate */
private static boolean parseArgs(String[] args) {
CommandLine cmd = new CommandLine ( cliArgs );
try {
cmd.parseArgs( args );
if (cmd.isUsageHelpRequested()) {
cmd.usage( cmd.getOut() );
return false;
}
else if ( cmd.isVersionHelpRequested() ) {
cmd.printVersionHelp (cmd.getOut());
return false;
}
logger.info("{}", cliArgs);
return true;
}
catch ( ParameterException ex ) {
logger.error ("Failure to parse : {}", ex);
return false;
}
}
private void execute() {
// execution logic
}
}
How do I avoid the boiler plate method, pargeArgs(String[])? The CliArguments class, technically, should not implement a Callable or Runnable. I can make MyApp be a Callable or Runnable. but to CommandLine, new MyApp() is not a command, new CliArguments() is.
If I want to do something like this:
final int exitCode = new CommandLine(new MyApp()).execute(args);
if (0 != exitCode) {
logger.error("Failed to parse");
System.exit(exitCode);
}
how do I push off all the #Option specification to a different class, CliArguments while still having the execution control in MyApp?
I am sure I am missing something straight forward.
The simplest way to achieve this is by making CliArguments a mixin in MyApp. We can then put the business logic in MyApp, and make it a Runnable or Callable so we can bootstrap the application with new CommandLine(new MyApp()).execute(args).
For example:
#Command(mixinStandardHelpOptions = true, version = "1.0.0")
public class CliArgs {
#Option(names = "-x") boolean x;
#Option(names = "-y") boolean y;
}
#Command(name = "myapp", description = "...")
public class MyApp implements Runnable {
// options defined in the mixin are added to this command
// also, #Command attributes from the mixin are applied to this command
#Mixin
CliArgs cliArgs;
public void run() {
System.out.printf("-x=%s%n", cliArgs.x);
System.out.printf("-y=%s%n", cliArgs.y);
}
public void main(String... args) {
System.exit(new CommandLine(new MyApp()).execute(args));
}
}
The options defined in the CliArgs mixin become part of the MyApp mixee.
Also, any #Command attributes defined in CliArgs become part of the MyApp command.
You can now run:
java MyApp -x
and this will print
-x=true
-y=false
Since the mixin has #Command(mixinStandardHelpOptions = true), the MyApp command also has --help and --version options that work as you would expect.

HealthChecksUI + memory (GCInfo)

How would you go about displaying Garbage Collection (GC) info in HealthChecksUI in .NET Core 3?
NuGet ref: https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks
I've been looking at samples here and there, but can't really find what I'm looking for. What I'm trying to do is do present the GC memory allocation to HC-UI and report degraded if it surpasses some limit. And I got it working - but I believe the implementation can be a lot better due to heap allocation when checking it.
Concerned section marked with comment # startup.cs
Here's my example:
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.
.AddCustomHealthChecks()
.AddHealthChecksUI();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app
.UseRouting()
.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = (check) =>
check.Tags.Contains("self")
|| check.Tags.Contains("memory"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = (check) => check.Tags.Contains("self")
});
endpoints.MapHealthChecksUI(setup =>
{
setup.AddCustomStylesheet("healthcheck-ui.css");
});
});
}
public static IServiceCollection AddCustomHealthChecks(this IServiceCollection services)
{
services
.AddHealthChecks()
.AddCheck(
"Self", () =>
HealthCheckResult.Healthy("Dynamic Config is OK!"),
tags: new[] { "self" }
)
.AddCheck("Memory", () =>
new GCInfoHealthCheck() // This section right here
.CheckHealthAsync( // seems to be a very
new HealthCheckContext() // poor implementation due
).GetAwaiter().GetResult(), // to constant Heap allocation.
tags: new[] { "memory" }
);
return services;
}
GCInfoHealthCheck.cs
public class GCInfoHealthCheck : IHealthCheck
{
public string Name { get; } = "GCInfo";
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{
var allocatedMegaBytes = GC.GetTotalMemory(forceFullCollection: false) / 1000000; // divided to get MB
var data = new Dictionary<string, object>()
{
{ "AllocatedMegaBytes", allocatedMegaBytes }
};
var status = (allocatedMegaBytes >= 20) ? HealthStatus.Unhealthy : HealthStatus.Healthy;
return Task.FromResult(
new HealthCheckResult(
status,
exception: null,
description: $"reports degraded status if allocated MB >= 20MB, current: {allocatedMegaBytes} MB",
data: data
)
);
}
}
Output
Here are some samples I've been looking at: https://github.com/aspnet/Diagnostics/tree/d1cba1f55bab1e3b206a46cee81eb1583d8732e2/samples/HealthChecksSample
I've been trying some other samples and register HC's as singletons, but I can't get it to work and report to HC-UI. So, is there better way to do it?
Seems like I've been a potato and probably misspelled the tag, because this works like a charm:
// register
.AddCheck<GCInfoHealthCheck>(
"memory",
failureStatus: HealthStatus.Unhealthy,
tags: new[] { "memory" }
)
// useage
.UseHealthChecks("/health", new HealthCheckOptions()
{
Predicate = (check) =>
|| check.Tags.Contains("memory"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
})
Docs: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1

AutoFixture: mock methods don't return a frozen instance

I'm trying to write this simple test:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var postProcessingAction = fixture.Freeze<Mock<IPostProcessingAction>>();
var postProcessor = fixture.Freeze<PostProcessor>();
postProcessor.Process("", "");
postProcessingAction.Verify(action => action.Do());
The Verify check fails.
The code for postProcessor.Process is
public void Process(string resultFilePath, string jobId)
{
IPostProcessingAction postProcessingAction =
postProcessingActionReader
.CreatePostProcessingActionFromJobResultXml(resultFilePath);
postProcessingAction.Do();
}
postProcessingActionReader is an interface field initialized through the constructor.
I'm expecting the test to pass but it fails, it turns out the instance of IPostProessingAction returned from the CreatePostProcessingActionFromJobResultXml method is not the same instance as returned from fixture.Freeze<>.
My expectation was that after freezing this Mock object it would inject the underlying mock of the IPostProcessingAction interface in every place its required as well as make all mock methods returning IPostProcessingAction return this same object.
Is my expectation about the return value of the mock methods incorrect?
Is there a way to change this behavior so that mock methods return the same frozen instance?
You need to Freeze the IPostProcessingActionReader component.
The following test will pass:
[Fact]
public void Test()
{
var fixture = new Fixture()
.Customize(new AutoMoqCustomization());
var postProcessingActionMock = new Mock<IPostProcessingAction>();
var postProcessingActionReaderMock = fixture
.Freeze<Mock<IPostProcessingActionReader>>();
postProcessingActionReaderMock
.Setup(x => x.CreatePostProcessingActionFromJobResultXml(
It.IsAny<string>()))
.Returns(postProcessingActionMock.Object);
var postProcessor = fixture.CreateAnonymous<PostProcessor>();
postProcessor.Process("", "");
postProcessingActionMock.Verify(action => action.Do());
}
Assuming that the types are defined as:
public interface IPostProcessingAction
{
void Do();
}
public class PostProcessor
{
private readonly IPostProcessingActionReader actionReader;
public PostProcessor(IPostProcessingActionReader actionReader)
{
if (actionReader == null)
throw new ArgumentNullException("actionReader");
this.actionReader = actionReader;
}
public void Process(string resultFilePath, string jobId)
{
IPostProcessingAction postProcessingAction = this.actionReader
.CreatePostProcessingActionFromJobResultXml(resultFilePath);
postProcessingAction.Do();
}
}
public interface IPostProcessingActionReader
{
IPostProcessingAction CreatePostProcessingActionFromJobResultXml(
string resultFilePath);
}
In case you use AutoFixture declaratively with the xUnit.net extension the test could be simplified even further:
[Theory, AutoMoqData]
public void Test(
[Frozen]Mock<IPostProcessingActionReader> readerMock,
Mock<IPostProcessingAction> postProcessingActionMock,
PostProcessor postProcessor)
{
readerMock
.Setup(x => x.CreatePostProcessingActionFromJobResultXml(
It.IsAny<string>()))
.Returns(postProcessingActionMock.Object);
postProcessor.Process("", "");
postProcessingActionMock.Verify(action => action.Do());
}
The AutoMoqDataAttribute is defined as:
internal class AutoMoqDataAttribute : AutoDataAttribute
{
internal AutoMoqDataAttribute()
: base(new Fixture().Customize(new AutoMoqCustomization()))
{
}
}
As of 3.20.0, you can use AutoConfiguredMoqCustomization. This will automatically configure all mocks so that their members' return values are generated by AutoFixture.
In other words, it will auto-configure your postProcessingActionReader to return the frozen postProcessingAction.
Just change this:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
to this:
var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());

Resources