Cuando estás explorando algo nuevo es muy típico crear una aplicación de consola y comentar/descomentar código para ver en funcionamiento lo que estás probando. Pero, tarde o temprano, este flujo se vuelve tedioso, propenso a errores y termina siendo un galimatías
Por eso, y apoyándonos en el post Console application vs worker service vamos a crear una aplicación de consola que nos permita ejecutar múltiples ejemplos de forma sencilla.
La idea se inspirá principalmente en un evento al que acudí hace mucho tiempo y que me gustó mucho https://github.com/CarlosLanderas/CSharp-6-and-7-features
La idea es ofrecer al usuario un menú simple e instanciar dinámicamente una clase que implemente IRunnable
.
Aunque en el post anterior se explica en detalle, habría que instalar los siguientes paquetes:
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.Configuration.UserSecrets
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Logging.Console
Para el fichero appsettings.json
:
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Y para los secretos de usuario lo dejo a tu elección, ya sea usando VS, Rider o dotnet user-secrets.
Finalmente, el código es el siguiente, ready for copy-paste:
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddUserSecrets(Assembly.GetExecutingAssembly())
.Build();
var services = new ServiceCollection();
services
.AddTransient<IConfiguration>(_ => configuration)
.AddLogging(configure => configure
.AddConfiguration(configuration.GetSection("Logging"))
.AddConsole())
RegisterExamples();
await RunExamplesAsync();
return;
async Task RunExamplesAsync()
{
var examples = GetExamples();
var i = 1;
foreach (var example in examples)
{
Console.WriteLine($"{i++}. {example}");
}
Console.WriteLine($"{i}. Exit");
Console.Write("Enter the number of the example to run: ");
while (true)
{
var input = Console.ReadLine();
if (int.TryParse(input, out var exampleIndex) && exampleIndex > 0 && exampleIndex <= examples.Length)
{
using var scope = services.BuildServiceProvider().CreateScope();
await scope.ServiceProvider.GetRequiredKeyedService<IRunnable>(examples.ElementAt(exampleIndex - 1))
.RunAsync();
}
else if (int.TryParse(input, out var exitIndex) && exitIndex == i)
{
break;
}
else
{
Console.WriteLine("Invalid input");
}
}
}
void RegisterExamples() =>
Assembly.GetExecutingAssembly().GetTypes()
.Where(t => typeof(IRunnable).IsAssignableFrom(t) && t is { IsInterface: false, IsAbstract: false })
.Select(t => t)
.ToList()
.ForEach(t => { services.AddKeyedTransient(typeof(IRunnable), t.Name, t); });
string[] GetExamples()
{
return services.Where(sd => sd.ServiceType == typeof(IRunnable))
.Select(sd => (string)sd.ServiceKey!)
.ToArray();
}
internal interface IRunnable
{
Task RunAsync();
}
internal class Example1(ILogger<Example1> logger) : IRunnable
{
public Task RunAsync()
{
logger.LogInformation("Example1");
return Task.CompletedTask;
}
}
internal class Example2 : IRunnable
{
public Task RunAsync()
{
Console.WriteLine("Example2");
return Task.CompletedTask;
}
}
Un saludo!