When building ASP.NET Core 2 web applications, most projects are initializing Kestrel with the CreateDefaultBuilder. If you use this extension method to configure Kestrel, it is good to know what is set and what differs when you are running a Development configuration or any other configuration. In practice there are two differences. In this blog I’ll explain the two differences.
Most asp.net core 2 web are initialized with the CreateDefaultBuilder:
public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .Build(); }
MSDN: WebHost.CreateDefaultBuilder Method
The following defaults are applied to the returned WebHostBuilder: use Kestrel as the web server, set the ContentRootPath to the result of GetCurrentDirectory(), load IConfiguration from ‘appsettings.json’ and ‘appsettings.[EnvironmentName].json’, load IConfiguration from User Secrets when EnvironmentName is ‘Development’ using the entry assembly, load IConfiguration from environment variables, load IConfiguration from supplied command line args, configures the ILoggerFactory to log to the console and debug output, enables IIS integration, enables the ability for frameworks to bind their options to their default configuration sections, and adds the developer exception page when EnvironmentName is ‘Development’
CreateDefaultBuilder
In almost all applications, the method CreateDefaultBuilder is used to do the basic configuration. The default builder does a number of steps to initialize your web application:
UseKestrel
Specify Kestrel as the server to be used by the web host.
UseContentRoot
Sets the content root to the path returned by Directory.GetCurrentDirectory.
ConfigureAppConfiguration
Set the configuration for the application. In order of:
- appsettings.json
- appsettings.{Environment}.json
- User secrets when in Development mode
- Environment variables
- Command-line arguments
ConfigureLogging
Configure basic logging for application.
UseIISIntegration
Configures the port and base path the server should listen on when running in IIS using the AspNetCoreModule. The app will also be configured to capture startup errors.
UseDefaultServiceProvider
Configure the default Service Provider for Dependency injection.
Two of these steps are different in Development configuration. The ConfigureAppConfiguration step can load different configuration variables and the UseDefaultServiceProvider set an extra option. It is important to know that you have a different version when using the Development setting for ASPNETCORE_ENVIRONMENT.
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }) .ConfigureServices(services => { services.AddTransient(); }); return builder; }
User Secrets
The user secret manager allows you to load personal configuration variables. The secret manager tool keeps all your secrets on your local computer. You can set secrets by right click on you web project in visual studio, select the Manage Secrets menu item. You can now edit the secrets.json file. The file can be shared over multiple project:
{ "MySecret": "ValueOfMySecret" }
Be aware that the name Secret Manager is a bit misleading. The secrets are saved as plain text files on your computer. Settings managers would have been a better naming.
Validate Scopes
Validation of scopes is only true in a Development environment. The following 2 things are validated:
- Scoped services aren’t directly or indirectly resolved from the root service provider.
- Scoped services aren’t directly or indirectly injected into singletons.
If you have code that does not inject correctly, the scoped objects gets the wrong lifespan. A scoped object with the lifespan of a singleton is probably not OK. When initializing the singleton you will see the following error:
System.InvalidOperationException: Cannot consume scoped service ‘MyDbContext’ from singleton ‘IMySingleton’.
Normally teams will run into this error when they develop, however I have seen many occasions that all team members do have there own environment name. Then the check will not occur, and they will never see the error. The problem will probably occur after some time on production. So finding and fixing it will be really hard.
Final thoughts
You should really run your code in a Development environment to test if there are any scoping issue in your dependency injection. If you leave them in there, your software will probably fail in a future scenario. We have seen memory leaks related to this, or objects in a zombie state. Do not make different environments for your developer, use the User Secrets for personal setting management.
One thought on “WebHost CreateDefaultBuilder, what differs if you run in Development”