Using Startup class in ASP.NET5 Console Application - c#

Is it possible for an ASP.NET 5-beta4 console application (built from the ASP.NET Console project template in VS2015) to use the Startup class to handle registering services and setting up configuration details?
I've tried to create a typical Startup class, but it never seems to be called when running the console application via dnx . run or inside Visual Studio 2015.
Startup.cs is pretty much:
public class Startup
{
public Startup(IHostingEnvironment env)
{
Configuration configuration = new Configuration();
configuration.AddJsonFile("config.json");
configuration.AddJsonFile("config.{env.EnvironmentName.ToLower()}.json", optional: true);
configuration.AddEnvironmentVariables();
this.Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<Settings>(Configuration.GetSubKey("Settings"));
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationContext>(options => options.UseSqlServer(this.Configuration["Data:DefaultConnection:ConnectionString"]));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(minLevel: LogLevel.Warning);
}
}
I've tried to manually create the Startup class in my Main method, but this doesn't seem like the right solution and hasn't so far allowed me to configure the services.
I'm assuming there's some way for me to create a HostingContext that doesn't start up a web server but will keep the console application alive. Something along the lines of:
HostingContext context = new HostingContext()
{
ApplicationName = "AppName"
};
using (new HostingEngine().Start(context))
{
// console code
}
However so far the only way I can get this to work is if I set the HostingContext.ServerFactoryLocation to Microsoft.AspNet.Server.WebListener, which starts up the web server.

What you're looking for is the right idea, but I think you'll need to back up a moment.
Firstly, you may have noticed that your default Program class isn't using static methods anymore; this is because the constructor actually gets some dependency injection love all on its own!
public class Program
{
public Program(IApplicationEnvironment env)
{
}
public void Main(string[] args)
{
}
}
Unfortunately, there aren't as many of the services you're used to from an ASP.NET 5 hosting environment registered; thanks to this article and the IServiceManifest you can see that there's only a few services available:
Microsoft.Framework.Runtime.IAssemblyLoaderContainer
Microsoft.Framework.Runtime.IAssemblyLoadContextAccessor
Microsoft.Framework.Runtime.IApplicationEnvironment
Microsoft.Framework.Runtime.IFileMonitor
Microsoft.Framework.Runtime.IFileWatcher
Microsoft.Framework.Runtime.ILibraryManager
Microsoft.Framework.Runtime.ICompilerOptionsProvider
Microsoft.Framework.Runtime.IApplicationShutdown
This means you'll get the joy of creating your own service provider, too, since we can't get the one provided by the framework.
private readonly IServiceProvider serviceProvider;
public Program(IApplicationEnvironment env, IServiceManifest serviceManifest)
{
var services = new ServiceCollection();
ConfigureServices(services);
serviceProvider = services.BuildServiceProvider();
}
private void ConfigureServices(IServiceCollection services)
{
}
This takes away a lot of the magic that you see in the standard ASP.NET 5 projects, and now you have the service provider you wanted available to you in your Main.
There's a few more "gotchas" in here, so I might as well list them out:
If you ask for an IHostingEnvironment, it'll be null. That's because a hosting environment comes from, well, ASP.Net 5 hosting.
Since you don't have one of those, you'll be left without your IHostingEnvironment.EnvironmentName - you'll need to collect it from the environment variables yourself. Which, since you're already loading it into your Configuration object, shouldn't be a problem. (It's name is "ASPNET_ENV", which you can add in the Debug tab of your project settings; this is not set for you by default for console applications. You'll probably want to rename that, anyway, since you're not really talking about an ASPNET environment anymore.)

Related

Implement full logging in Integration Test

I'm creating a new app in .Net Core 3.1.
I have the database created, and now I'm working on the business logic, which is in a collection of services. Before I start to create the API or the UI (ie: any web-app type project), I want to 100% get all of the Data Access and Services working as expected first... including logging. To make sure this is all working together as it should, I want to create some integration tests.
The problem I am running into is I am REALLY struggling with how to get logging working. Every tutorial, document, and all of the examples I can find assume you have a Program.cs and Startup.cs.
NOTE 1: I want the logging to work just as it would in production, which means all the way to SQL. No mocking. No substitution. No replacing.
NOTE 2: I don't care so much about logging from the test. I want logging to work in the service.
Here is an example of an integration test (xUnit) class that I have so far. It's working but has no logging.
namespace MyApp.Test
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IAccountService, AccountService>();
services.AddTransient<IAppSettings, AppSettings>();
}
}
public class AccountServiceTests
{
private readonly IAccountService _account;
private readonly IAppSettings _settings;
public AccountServiceTests(IAccountService account, IAppSettings settings)
{
_account = account;
_settings = settings;
}
[Fact]
public async Task AccountService_CreateAccount()
{
Account account = new Account( {...} );
bool result = _account.CreateAccount(account);
Assert.True(result);
}
}
}
The DI is working because of NuGet Xunit.DependencyInjection.
And then in the service...
public class AccountService : ServiceBase, IAccountService
{
protected readonly ILogger _logger;
protected readonly IAppSettings _settings;
public AccountService(ILogger<AccountService> logger, IAppSettings settings)
{
_logger = logger;
_settings = settings;
}
public bool CreateAccount()
{
// do stuff
_logger.Log(LogLevel.Information, "An account was created."); // I WANT THIS TO END UP IN SQL, EVEN FROM THE TEST.
}
}
The test passes, and the account is properly created in the database. However, as best as I can tell, this line doesn't do anything:
_logger.Log(LogLevel.Information, "An account was created.");
I understand why. Microsoft.Extensions.Logging is just an abstraction, and I need to implement some concrete logging (with SeriLog or Log4Net, etc.)
This brings me back to my original question: For the life of me, I can not find a working tutorial on how to get either one of those (SeriLog or Log4Net) working within an integration test (xUnit in particular).
Any help, or point in the right direction, or a link to a tutorial would be wonderful. Thanks.
Add Logging to the service collection using LoggingServiceCollectionExtensions.AddLogging
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddLogging(builder =>
builder.ClearProviders()
.Add{ProverNameHere}()
// ... other logging configuration
);
services.AddTransient<IAccountService, AccountService>();
services.AddTransient<IAppSettings, AppSettings>();
}
}
This will add the factory and open generic for ILogger<> so that they can be injected where needed.
Configure the logging as desired for where that information should go.
There are built-in providers that ASP.NET Core includes as part of the shared framework, but since this is an isolated test you have to add the desired providers as needed.
For example
//...
services.AddLogging(builder => builder.AddConsole().AddDebug());
//...
Console
The Console provider logs output to the console.
Debug
The Debug provider writes log output by using the System.Diagnostics.Debug class. Calls to System.Diagnostics.Debug.WriteLine write to the Debug provider.
Logging output from dotnet run and Visual Studio
Logs created with the default logging providers are displayed:
In Visual Studio
In the Debug output window when debugging.
In the ASP.NET Core Web Server window.
In the console window when the app is run with dotnet run.
Logs that begin with "Microsoft" categories are from ASP.NET Core framework code. ASP.NET Core and application code use the same logging API and providers.
Reference: Logging in .NET Core and ASP.NET Core
Configure a logger factory
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddConsole()
.AddEventLog();
});
and then register it as a service
services.AddSingleton(loggerFactory);
If you just want to see that the logging is working in xUnit, add something like the following code to the constructor of your xUnit test, and pass it on to the base constructor as follows. (I'm assuming here you're using a WebApplicationFactory to start up the service in the test.)
protected readonly WebApplicationFactory<Startup> Factory;
public AccountServiceTests(IAccountService account, IAppSettings settings, ITestOutputHelper testOutputHelper) : base( testOutputHelper )
{
_account = account;
_settings = settings;
Factory = new WebApplicationFactory<Startup>().WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddLogging(logBuilder => logBuilder.AddXUnit(testOutputHelper));
});
});
}
This will cause your log messages to be shown in the output of the test.
Note that the details here might vary somewhat for what you have, but this is the basic idea: xUnit passes in the testOutputHelper and you use it to initialize the logging system for the test.
Programatically verifying that the proper log messages were written by your test is another matter. I don't have a ready answer for that.

How can I get the ASP.NET Core environment in a static helper method?

I can't use dependency injection, I have static helper class with static methods in my ASP.NET Core project. What is the easiest way to determine that app is running in development environment?
Equivalent of:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) // -> this env is what I'm looking for
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
...
}
How can I get env manually without DI?
Considering that the Startup is the very first thing that runs on an ASP.NET Core application (after the host is built in the main Program class), there are very simple solutions available:
public class Startup
{
public static IWebHostEnvironment Environment { get; }
public Startup(IWebHostEnvironment environment)
{
Environment = environment;
}
}
Note that the correct solution is, however, not to mix Dependency Injection with static classes.

How to call services.Configure from Configure method in Startup.cs

In ConfigureServices() in my Startup.cs file I call this
if (services.BuildServiceProvider().GetService<IWebHostEnvironment>().IsDevelopment()) {
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesDevelopment"));
} else {
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesProduction"));
}
I'm getting a build warning saying
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'
How do I call this from Configure() with the IApplicationBuilder?
First, check multiple environments support in ASP.NET Core. You could configure environment specific sections to avoid if-else statement in code.
Now assume the above does not work for you:
You should avoid building container in ConfigureServices method as per the reason stated in the warning message.
Instead, you should use the options pattern:
services.AddOptions<EnvironmentSettings>()
.Configure<IConfiguration, IWebHostEnvironment>(
(settings, config, env) =>
{
if (env.IsDevelopment())
{
config.GetSection("WebServicesDevelopment").Bind(settings);
}
else
{
config.GetSection("WebServicesProduction").Bind(settings);
}
});
As the warning states, calling BuildServiceProvider can have unintended side effects and is an anti-pattern. Services are meant to be configured in ConfigureServices and we have all the necessary tools to perform conditional configuration based on environment. Simply inject IWebHostEnvironment in the constructor for the Startup class and configure your services appropriately:
public class Startup
{
private readonly IWebHostEnvironment _env;
public Startup(IConfiguration configuration, IWebHostEnvironment env /* take dependency on environment */)
{
Configuration = configuration;
_env = env; // store environment
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// configure based on environment
if (_env.IsDevelopment())
{
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesDevelopment"));
}
else
{
// non-dev services
services.Configure<EnvironmentSettings>(Configuration.GetSection("WebServicesProduction"));
}
}
}
See the following documentation: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.1 search for the phrase "Only the following service types can be injected"
This link has more information, including how you can even have multiple Startup classes or multiple ConfigureServices methods (per environment)

"Microsoft.Extensions.DependencyInjection" and different concretes per environment

"Microsoft.Extensions.DependencyInjection"
List of "environments"
Production
Uat
Qa
DevShared
LocalDev
With DotNet (Framework/Classic) 4.6 or above (aka, "in the past", I used "Unity" with xml configuration. https://blogs.msdn.microsoft.com/miah/2009/04/03/testing-your-unity-xml-configuration/
(In the past before Dot Net core when using "Unity" IoC/DI)...When I had a need to have a concrete specific to an environment, I would tweak the concrete on the .xml.
For instance, let's say my webApi needed authentication in production, uat, qa and dev-shared. but in dev-local, I do not want to deal with authentication all the time as I developed the webApi, I would have 2 concretes.
IAuthorizer
MyRealAuthorizer : IAuthorizer
MyDevLetEverythingThroughAuthorizer : IAuthorizer
and I would "register" one of them .. using xml.
My build process would alter the unity.xml (unity.config to be precise) and change out (via xml-update-tasks in msbuild)
MyDevLetEverythingThroughAuthorizer
to
MyRealAuthorizer
.
.....
Java Spring has "annotation" based:
import org.springframework.context.annotation.Profile;
#Profile("localdev")
public class MyDevLetEverythingThroughAuthorizer implements IAuthorizer {
#Profile("!localdev")
public class MyRealAuthorizer implements IAuthorizer {
But that does not honor the "Composite Root" pattern : (Mark Seeman http://blog.ploeh.dk/2011/07/28/CompositionRoot/ )
.......
So now I'm entering the world of DotNetCore. Everything has been going smooth. But I finally hit a situation where I need a dev-friendly concrete vs a non-dev "real" concretes.
Xml isn't available (to my best knowledge) with "Microsoft.Extensions.DependencyInjection".
I'm not sure of the best practice with DotNetCore in this situation.
I would prefer to honor the Composite Root pattern.
Basically, the below......but respecting the environments.
asp.net'ish
public void ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
/* need this for "local-dev" */
services.AddScoped<IAuthorizer, MyDevLetEverythingThroughAuthorizer>();
/* need this for everything EXCEPT "local-dev" */
services.AddScoped<IAuthorizer, MyRealAuthorizer>();
}
(not asp.net) dot.net core'ish too
private static System.IServiceProvider BuildDi()
{
//setup our DI
IServiceProvider serviceProvider = new ServiceCollection()
.AddLogging()
/* need this for "local-dev" */
.AddSingleton<IAuthorizer, MyDevLetEverythingThroughAuthorizer>()
/* need this for everything EXCEPT "local-dev" */
.AddSingleton<IAuthorizer, MyRealAuthorizer>()
APPEND
This article and snipplet help me understand the "what is built in" portion a little better:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.2
Environments ASP.NET Core reads the environment variable
ASPNETCORE_ENVIRONMENT at app startup and stores the value in
IHostingEnvironment.EnvironmentName. You can set
ASPNETCORE_ENVIRONMENT to any value, but three values are supported by
the framework: Development, Staging, and Production. If
ASPNETCORE_ENVIRONMENT isn't set, it defaults to Production.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Staging_2"))
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
The
env.IsEnvironment("Staging_2") (akin to env.IsEnvironment("MyCustomValue") ) is the trick I guess.
APPEND:
This SOF question made it more clear for Asp.Net Core.
How to set aspnetcore_environment in publish file?
And ways you can set the environment variable without actually setting a (machine) environment variable!
public void ConfigureServices(IServiceCollection services, IHostingEnvironment environment) {
if (environment.IsDevelopment()) {
// bla bla bla
} else {
// bla bla bla
}
// register no-matter-which-environment services
}
Your question seems to be talking about two things: setting configuration from XML files and managing services using IServiceCollection. For .net core web applications, these happen in two stages:
A key value pair is consolidated from various pre-defined and custom sources (including json, XML, environment). All preset .net core web templates do this in program.cs.
The key value pair collection is sent to the Startup class that can be accessed via DI from the IConfiguration variable. Check this link for more information.
With this being the process, all config files are added before the ConfigureServices method is called in the Startup class. If you would like to add XML files to this configuration, you can set the following code in your program.cs:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
// additional code
.UseConfiguration(
new ConfigurationBuilder()
.AddXmlFile("file.xml", true) // second param is if file is optional
.Build()
)
// end of additional code
.UseStartup<Startup>();
If you want to access your environment, if they are set as environment variables, you can use one of the Environment.Get... functions.
Regarding your service, I am not sure in what way you are trying to access your XML, but you can always inject the IConfiguration as the simplest solution if you need to. However, I would advise against exposing your entire configuration to your services and have a look at setting up options with the help of this documentation

What is the proper place for db seeding from json files in ASP.NET Core 2.0?

In my Core 1.0 app, Startup ctor loaded several extra json files into the configuration...
public Startup(IHostingEnvironment env) {
Environment = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json")
.AddJsonFile($"activityGroups.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Then in ConfigureServices, I grabbed the data from these out of the config into strongly typed lists...
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<Models.DbContext>(
options => options.UseSqlServer(
Configuration["Data:DefaultConnection:ConnectionString"]
)
);
services.AddScoped<Repository>();
services.AddTransient<DbSeed>();
services.AddOptions();
services.Configure<List<Models.Task>>(Configuration.GetSection("ActivityGroups"));
}
Then, in Configure, the strongly typed list was injected and use for db init..
public void Configure(
IApplicationBuilder app,
DbSeed seed,
ILoggerFactory logging,
IOptions<List<ActivityGroup>> activityGroupConfig
) {
app.UseAuthentication();
app.UseMvc();
bool seedDb;
bool.TryParse(Configuration.GetSection("SeedDb").Value, out seedDb);
if (seedDb) {
seed.EnsureSeeded(activityGroupConfig.Value);
}
}
However...
In 2.0 projects, move the SeedData.Initialize call to the Main method
of Program.cs...
As of 2.0, it's bad practice to do anything in BuildWebHost except
build and configure the web host. [configure web host includes data seeding?] Anything that's about running the
application should be handled outside of BuildWebHost — typically in
the Main method of Program.cs.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code
Yet, in "new" Main(string[] args) we don't have access to the dbcontext (until Startup.ConfigureServices). Even if in the SeedData.Initialize(services); call as above in the MS documentation, how would SeedData have access to the connection string that is not defined until later in configure services?
The adoption of this new 2.0 pattern is highly recommended and is
required for product features like Entity Framework (EF) Core
Migrations to work.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#update-main-method-in-programcs
I've been googling and digging through docs and samples (that example seeds with hardcoded values) without getting a clear picture (yet) of the expectations or recommended design (beyond, "dont do what you did previously".)
Anyone here more familiar or experienced with how to do database seeding from json on disk in ASP.NET Core 2.0?
So, with help from this SO question, I got the gist of getting the services from within Program.cs
public class Program {
public static void Main(string[] args) {
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILoggerFactory>();
var ctx = services.GetRequiredService<DataContext>();
// Now I can access the DbContext, create Repository, SeedData, etc!
Now, the problem is in trying to figure out how to access values from appsettings.json, new SO question here.

Categories