The other day, I wanted to create a really simple console application using .NET Core 2.x. Out of the box, however, it appeared that there were a lot of compromises to a console application as compared to a Web/Kestrel hosted app. The main things that were missing were dependency injection and user secrets.
To get dependency injection working, I like to create a Startup.cs that, somewhat, mirrors a .NET Core Web application.
Essentially we create a configuration builder and a ServiceCollection. The ServiceCollection and IServiceProvider. The ServiceCollection is, really, our DI container. The Main method it will look very similar to a web app. It will also execute our Application by getting an instance of it and calling the “Run” method.
public static void Main(string[] args) { string env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); string launch = Environment.GetEnvironmentVariable("LAUNCH_PROFILE"); if (string.IsNullOrWhiteSpace(env)) { env = "Development"; } var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); if (env == "Development") { builder.AddUserSecrets<Startup>(); } Configuration = builder.Build(); // Create a service collection and configure our depdencies var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); // Build the our IServiceProvider and set our static reference to it ServiceProviderFactory.ServiceProvider = serviceCollection.BuildServiceProvider(); // Enter the applicaiton.. (run!) ServiceProviderFactory.ServiceProvider.GetService<Application>().Run(); }
Note the call to a method called “ConfigureServices.” This is where are of the dependencies are configured. Also note the single line that calls “AddUserSecrets.” That will be discussed a bit more later.
private static void ConfigureServices(IServiceCollection services) { // Make configuration settings available services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.AddSingleton<IConfiguration>(Configuration); var appSettings = new AppSettings(); Configuration.GetSection("AppSettings").Bind(appSettings); // Some libraries may still rely on web context type stuff.. services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddTransient<IViewRenderService, ViewRenderService>(); services.AddTransient<ICurrentUserService, CurrentUserService>(); var appConnStr = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "WindowsConnStr" : "LinuxConnStr"; services.AddDbContext<MyDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString(appConnStr)); }, ServiceLifetime.Scoped); // Repositories services.AddScoped(typeof(IRepository<>), typeof(DomainRepository<>)); // Add AutoMapper var config = new AutoMapper.MapperConfiguration(cfg => { cfg.AddProfile(new AutoMapperProfile()); }); var mapper = config.CreateMapper(); services.AddSingleton(mapper); // Add caching services.AddMemoryCache(); // Add logging services.AddLogging(builder => { builder.AddConfiguration(Configuration.GetSection("Logging")) .AddConsole() .AddDebug(); }); // Add Application services.AddTransient<Application>(); }
Note the last service that is added is our “Application.” This is synonymous with the default Program.cs that is created when you start a new console application. Creating a Startup class similar to this allows our Application class to have whatever dependencies injected. Sure, this may seem like a long way to go, but if you have a ton of dependencies, you’re migrating a web app to a console app, or have another similar scenario, using DI, getting the environmental niceties of a typical Web app, and other features within your console application can be very convenient.
One of those nice features is user secrets. As I pointed out above, you can import your secrets using the command “AddUserSecrets” builder extension method. Typically, user secrets are only available for web applcation. It’s pretty easy to get them working in the console application. The only limitation that I have found is that Visual Studio won’t give you the nice context menu for editing secrets. But, using the command line “dotnet user-secrets” works well enough. You can also browse to your AppData folder and modify this JSON file directly.
To enable user secrets, we have to include modify our project a bit. First, the header should be modified so that we give our console applicaiton a unique ID for its secrets:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> <UserSecretsId>MyUniqueId</UserSecretsId> </PropertyGroup>
Next, these nuget packages should be included:
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
And these tools must be present in the build chain:
<ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" /> <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" /> </ItemGroup>
After that, we can open a command prompt within the directory that contains the console application project and manage the user secrets there.
That’s really it. With all this set-up in place, we get DI, user secrets, and general environment handling with our console application.