Skip to content

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

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 :(

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions