Remove IServerLoader and add the IServerFactory to the DI container

- This change removes the indirection and between the IServerLoader and
the IServerFactory. We now add the IServerFactory directly to the DI
container and resolve it from there.
- Moved logic that resolves IServerFactory from an assembly to a static
helper
This commit is contained in:
David Fowler 2016-03-29 22:40:42 -07:00
parent 8451d1afcb
commit 1182a5a9ca
11 changed files with 125 additions and 140 deletions

View File

@ -26,13 +26,6 @@ namespace Microsoft.AspNetCore.Hosting
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory);
/// <summary>
/// Specify the <see cref="IServerFactory"/> to be used by the web host.
/// </summary>
/// <param name="factory">The <see cref="IServerFactory"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder UseServer(IServerFactory factory);
/// <summary>
/// Specify the startup type to be used by the web host.
/// </summary>

View File

@ -0,0 +1,31 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting.Server;
namespace Microsoft.AspNetCore.Hosting.Internal
{
internal static class ServerLoader
{
internal static Type ResolveServerFactoryType(string assemblyName)
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
if (assembly == null)
{
throw new InvalidOperationException(string.Format("The assembly {0} failed to load.", assemblyName));
}
var serverTypeInfo = assembly.DefinedTypes.Where(
t => t.ImplementedInterfaces.FirstOrDefault(interf => interf.Equals(typeof(IServerFactory))) != null)
.FirstOrDefault();
if (serverTypeInfo == null)
{
throw new InvalidOperationException($"No server type found that implements IServerFactory in assembly: {assemblyName}.");
}
return serverTypeInfo.AsType();
}
}
}

View File

@ -31,14 +31,16 @@ namespace Microsoft.AspNetCore.Hosting.Internal
private RequestDelegate _application;
private ILogger<WebHost> _logger;
// Used for testing only
internal WebHostOptions Options => _options;
// Only one of these should be set
internal string StartupAssemblyName { get; set; }
internal StartupMethods Startup { get; set; }
internal Type StartupType { get; set; }
// Only one of these should be set
internal IServerFactory ServerFactory { get; set; }
internal string ServerFactoryLocation { get; set; }
private IServerFactory ServerFactory { get; set; }
private IServer Server { get; set; }
public WebHost(
@ -207,18 +209,9 @@ namespace Microsoft.AspNetCore.Hosting.Internal
{
if (Server == null)
{
if (ServerFactory == null)
{
// Blow up if we don't have a server set at this point
if (ServerFactoryLocation == null)
{
throw new InvalidOperationException("IHostingBuilder.UseServer() is required for " + nameof(Start) + "()");
}
ServerFactory = _applicationServices.GetRequiredService<IServerLoader>().LoadServerFactory(ServerFactoryLocation);
}
ServerFactory = _applicationServices.GetRequiredService<IServerFactory>();
Server = ServerFactory.CreateServer(_config);
var addresses = Server.Features?.Get<IServerAddressesFeature>()?.Addresses;
if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
{

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
DetailedErrors = ParseBool(configuration, WebHostDefaults.DetailedErrorsKey);
CaptureStartupErrors = ParseBool(configuration, WebHostDefaults.CaptureStartupErrorsKey);
Environment = configuration[WebHostDefaults.EnvironmentKey];
ServerFactoryLocation = configuration[WebHostDefaults.ServerKey];
ServerFactoryAssembly = configuration[WebHostDefaults.ServerKey];
WebRoot = configuration[WebHostDefaults.WebRootKey];
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
}
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public string Environment { get; set; }
public string ServerFactoryLocation { get; set; }
public string ServerFactoryAssembly { get; set; }
public string WebRoot { get; set; }

View File

@ -1,10 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Hosting.Server
{
public interface IServerLoader
{
IServerFactory LoadServerFactory(string assemblyName);
}
}

View File

@ -1,50 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Hosting.Server
{
public class ServerLoader : IServerLoader
{
private readonly IServiceProvider _services;
public ServerLoader(IServiceProvider services)
{
_services = services;
}
public IServerFactory LoadServerFactory(string assemblyName)
{
if (assemblyName == null)
{
throw new ArgumentNullException(nameof(assemblyName));
}
if (string.IsNullOrEmpty(assemblyName))
{
throw new ArgumentException(string.Empty, nameof(assemblyName));
}
var assembly = Assembly.Load(new AssemblyName(assemblyName));
if (assembly == null)
{
throw new Exception(string.Format("The assembly {0} failed to load.", assemblyName));
}
var serverTypeInfo = assembly.DefinedTypes.Where(
t => t.ImplementedInterfaces.FirstOrDefault(interf => interf.Equals(typeof(IServerFactory))) != null)
.FirstOrDefault();
if (serverTypeInfo == null)
{
throw new InvalidOperationException($"No server type found that implements IServerFactory in assembly: {assemblyName}.");
}
return (IServerFactory)ActivatorUtilities.GetServiceOrCreateInstance(_services, serverTypeInfo.AsType());
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Builder;
@ -38,9 +39,6 @@ namespace Microsoft.AspNetCore.Hosting
private StartupMethods _startup;
private Type _startupType;
// Only one of these should be set
private IServerFactory _serverFactory;
/// <summary>
/// Initializes a new instance of the <see cref="WebHostBuilder"/> class.
/// </summary>
@ -89,22 +87,6 @@ namespace Microsoft.AspNetCore.Hosting
return this;
}
/// <summary>
/// Specify the <see cref="IServerFactory"/> to be used by the web host.
/// </summary>
/// <param name="factory">The <see cref="IServerFactory"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder UseServer(IServerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
_serverFactory = factory;
return this;
}
/// <summary>
/// Specify the startup type to be used by the web host.
/// </summary>
@ -189,10 +171,6 @@ namespace Microsoft.AspNetCore.Hosting
var host = new WebHost(hostingServices, startupLoader, _options, _config);
// Only one of these should be set, but they are used in priority
host.ServerFactory = _serverFactory;
host.ServerFactoryLocation = _options.ServerFactoryLocation;
// Only one of these should be set, but they are used in priority
host.Startup = _startup;
host.StartupType = _startupType;
@ -227,8 +205,6 @@ namespace Microsoft.AspNetCore.Hosting
services.AddLogging();
services.AddTransient<IStartupLoader, StartupLoader>();
services.AddTransient<IServerLoader, ServerLoader>();
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
services.AddOptions();
@ -248,6 +224,13 @@ namespace Microsoft.AspNetCore.Hosting
services.AddSingleton(defaultPlatformServices.Application);
services.AddSingleton(defaultPlatformServices.Runtime);
if (!string.IsNullOrEmpty(_options.ServerFactoryAssembly))
{
// Add the server factory
var serverFactoryType = ServerLoader.ResolveServerFactoryType(_options.ServerFactoryAssembly);
services.AddSingleton(typeof(IServerFactory), serverFactoryType);
}
foreach (var configureServices in _configureServicesDelegates)
{
configureServices(services);

View File

@ -2,9 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Hosting
{
@ -104,6 +107,27 @@ namespace Microsoft.AspNetCore.Hosting
return hostBuilder.UseSetting(WebHostDefaults.ServerKey, assemblyName);
}
/// <summary>
/// Specify the <see cref="IServerFactory"/> to be used by the web host.
/// </summary>
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="factory">The <see cref="IServerFactory"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseServer(this IWebHostBuilder hostBuilder, IServerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return hostBuilder.ConfigureServices(services =>
{
// It would be nicer if this was transient but we need to pass in the
// factory instance directly
services.AddSingleton(factory);
});
}
/// <summary>
/// Specify the server to be used by the web host.
/// </summary>
@ -117,6 +141,8 @@ namespace Microsoft.AspNetCore.Hosting
throw new ArgumentNullException(nameof(server));
}
// It would be nicer if this was transient but we need to pass in the
// server instance directly
return hostBuilder.UseServer(new ServerFactory(server));
}

View File

@ -242,14 +242,14 @@ namespace Microsoft.AspNetCore.Hosting
public void CodeBasedSettingsCodeBasedOverride()
{
var hostBuilder = new WebHostBuilder()
.UseSetting(WebHostDefaults.ServerKey, "ServerA")
.UseSetting(WebHostDefaults.ServerKey, "ServerB")
.UseSetting(WebHostDefaults.EnvironmentKey, "EnvA")
.UseSetting(WebHostDefaults.EnvironmentKey, "EnvB")
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
Assert.Equal("EnvB", host.Options.Environment);
}
[Fact]
@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.Hosting
{
var settings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerB" }
{ WebHostDefaults.EnvironmentKey, "EnvB" }
};
var config = new ConfigurationBuilder()
@ -265,14 +265,14 @@ namespace Microsoft.AspNetCore.Hosting
.Build();
var hostBuilder = new WebHostBuilder()
.UseSetting(WebHostDefaults.ServerKey, "ServerA")
.UseSetting(WebHostDefaults.EnvironmentKey, "EnvA")
.UseConfiguration(config)
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
Assert.Equal("EnvB", host.Options.Environment);
}
[Fact]
@ -280,7 +280,7 @@ namespace Microsoft.AspNetCore.Hosting
{
var settings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerA" }
{ WebHostDefaults.EnvironmentKey, "EnvA" }
};
var config = new ConfigurationBuilder()
@ -289,13 +289,13 @@ namespace Microsoft.AspNetCore.Hosting
var hostBuilder = new WebHostBuilder()
.UseConfiguration(config)
.UseSetting(WebHostDefaults.ServerKey, "ServerB")
.UseSetting(WebHostDefaults.EnvironmentKey, "EnvB")
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
Assert.Equal("EnvB", host.Options.Environment);
}
[Fact]
@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.Hosting
{
var settings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerA" }
{ WebHostDefaults.EnvironmentKey, "EnvA" }
};
var config = new ConfigurationBuilder()
@ -312,7 +312,7 @@ namespace Microsoft.AspNetCore.Hosting
var overrideSettings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerB" }
{ WebHostDefaults.EnvironmentKey, "EnvB" }
};
var overrideConfig = new ConfigurationBuilder()
@ -327,7 +327,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
Assert.Equal("EnvB", host.Options.Environment);
}
[Fact]

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests
var config = new WebHostOptions(new ConfigurationBuilder().AddInMemoryCollection(parameters).Build());
Assert.Equal("wwwroot", config.WebRoot);
Assert.Equal("Microsoft.AspNetCore.Server.Kestrel", config.ServerFactoryLocation);
Assert.Equal("Microsoft.AspNetCore.Server.Kestrel", config.ServerFactoryAssembly);
Assert.Equal("MyProjectReference", config.Application);
Assert.Equal("Development", config.Environment);
Assert.True(config.CaptureStartupErrors);

View File

@ -24,7 +24,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Hosting
{
public class WebHostTests : IServerFactory, IServer
public class WebHostTests : IServerFactory
{
private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
private IFeatureCollection _featuresSupportedByThisHost = NewFeatureCollection();
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostThrowsWithNoServer()
{
var ex = Assert.Throws<InvalidOperationException>(() => CreateBuilder().Build().Start());
Assert.True(ex.Message.Contains("UseServer()"));
Assert.Equal("No service for type 'Microsoft.AspNetCore.Hosting.Server.IServerFactory' has been registered.", ex.Message);
}
[Fact]
@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void CanDefaultAddresseIfNotConfigured()
public void CanDefaultAddressesIfNotConfigured()
{
var vals = new Dictionary<string, string>
{
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostCanBeStarted()
{
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.Start();
@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostShutsDownWhenTokenTriggers()
{
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.Build();
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostDisposesServiceProvider()
{
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.ConfigureServices(s =>
{
s.AddTransient<IFakeService, FakeService>();
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostNotifiesApplicationStarted()
{
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.Build();
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostInjectsHostingEnvironment()
{
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.UseEnvironment("WithHostingEnvironment")
.Build();
@ -237,7 +237,7 @@ namespace Microsoft.AspNetCore.Hosting
{
services.AddTransient<IStartupLoader, TestLoader>();
})
.UseServer((IServerFactory)this)
.UseServer(this)
.UseStartup("Microsoft.AspNetCore.Hosting.Tests");
Assert.Throws<NotImplementedException>(() => builder.Build());
@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Hosting
[Fact]
public void CanCreateApplicationServicesWithAddedServices()
{
var host = CreateBuilder().UseServer((IServerFactory)this).ConfigureServices(services => services.AddOptions()).Build();
var host = CreateBuilder().UseServer(this).ConfigureServices(services => services.AddOptions()).Build();
Assert.NotNull(host.Services.GetRequiredService<IOptions<object>>());
}
@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.Hosting
// Verify ordering
var configureOrder = 0;
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.ConfigureServices(services =>
{
services.AddTransient<IStartupFilter>(serviceProvider => new TestFilter(
@ -300,7 +300,7 @@ namespace Microsoft.AspNetCore.Hosting
[Fact]
public void EnvDefaultsToProductionIfNoConfig()
{
var host = CreateBuilder().UseServer((IServerFactory)this).Build();
var host = CreateBuilder().UseServer(this).Build();
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
}
@ -317,7 +317,7 @@ namespace Microsoft.AspNetCore.Hosting
.AddInMemoryCollection(vals);
var config = builder.Build();
var host = CreateBuilder(config).UseServer((IServerFactory)this).Build();
var host = CreateBuilder(config).UseServer(this).Build();
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal("Staging", env.EnvironmentName);
}
@ -334,7 +334,7 @@ namespace Microsoft.AspNetCore.Hosting
.AddInMemoryCollection(vals);
var config = builder.Build();
var host = CreateBuilder(config).UseServer((IServerFactory)this).Build();
var host = CreateBuilder(config).UseServer(this).Build();
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath);
Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists);
@ -343,7 +343,7 @@ namespace Microsoft.AspNetCore.Hosting
[Fact]
public void IsEnvironment_Extension_Is_Case_Insensitive()
{
var host = CreateBuilder().UseServer((IServerFactory)this).Build();
var host = CreateBuilder().UseServer(this).Build();
using (host)
{
host.Start();
@ -401,7 +401,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHost_InvokesConfigureMethodsOnlyOnce()
{
var host = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.UseStartup<CountStartup>()
.Build();
using (host)
@ -434,7 +434,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHost_ThrowsForBadConfigureServiceSignature()
{
var builder = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.UseStartup<BadConfigureServicesStartup>();
var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
@ -450,7 +450,7 @@ namespace Microsoft.AspNetCore.Hosting
private IWebHost CreateHost(RequestDelegate requestDelegate)
{
var builder = CreateBuilder()
.UseServer((IServerFactory)this)
.UseServer(this)
.Configure(
appBuilder =>
{
@ -497,7 +497,7 @@ namespace Microsoft.AspNetCore.Hosting
{
_instanceFeaturesSupportedByThisHost = new FeatureCollection();
_instanceFeaturesSupportedByThisHost.Set<IServerAddressesFeature>(new ServerAddressesFeature());
return this;
return new FakeServer(this);
}
private class StartInstance : IDisposable
@ -671,5 +671,24 @@ namespace Microsoft.AspNetCore.Hosting
{
public string TraceIdentifier { get; set; }
}
private class FakeServer : IServer
{
private readonly WebHostTests _webHostTests;
public FakeServer(WebHostTests webHostTests)
{
_webHostTests = webHostTests;
}
public IFeatureCollection Features => _webHostTests.Features;
public void Dispose() => _webHostTests.Dispose();
public void Start<TContext>(IHttpApplication<TContext> application)
{
_webHostTests.Start<TContext>(application);
}
}
}
}