Skip to content

The WebHostBuilder 2.0 causes all kinds of problems :( #47

@snebjorn

Description

@snebjorn

The new recommend way to start an asp net core 2.0 app is as follows

public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();

If we take a look at CreateDefaultBuilder then we get

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();
        });

    return builder;
}

and Startup is now expected to look like this

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}

Which causes problems for the TestEnvironment. Because the app is being setup outside Startup. Startup just adds a little flavor to the configuration it no longer creates/controls it.

Inside TestEnvironment we find CreateTestServer

protected virtual TestServer CreateTestServer()
{
    return new TestServer
    (
        new WebHostBuilder()
                .ConfigureStartup(new TStartupConfigurationService(), this.contentRootPath)
                .UseStartup<TStartup>()
    );
}

The problem with this is that the new WebHostBuilder() should reflect the configuration inside Main.BuildWebHost().

I tried solving this by deriving from TestEnvironment

public class MyTestEnviornment<TStartup, TStartupConfigurationService> : TestEnvironment<TStartup, TStartupConfigurationService>
{
    public MyTestEnviornment(string targetProjectRelativePath = null) : base(targetProjectRelativePath)
    {
    }

    protected override TestServer CreateTestServer()
    {
        return new TestServer
        (
            new WebHostBuilder()
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    var env = hostingContext.HostingEnvironment;
                    env.EnvironmentName = "Test";

                    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();
                })
                .ConfigureStartup(new TStartupConfigurationService(), this.contentRootPath)
                .UseStartup<TStartup>()
        );
    }
}

But that has a bunch of problems. this.contentRootPath is private and therefore inaccessible to MyTestEnviornment. Also note I have to do env.EnvironmentName = "Test"; because the below haven't been called yet.

// defined in IStartupConfigurationService
public void ConfigureEnvironment(IHostingEnvironment env)
{
    env.EnvironmentName = "Test";
}

I haven't figured out a good way of solving this. We need a way to control what gets added to WebHostBuilder. So a wrapper comes to mind, but then the nice "add-in" feeling disappears :(

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions