497 lines
17 KiB
C#
497 lines
17 KiB
C#
// 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.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNet.Builder;
|
|
using Microsoft.AspNet.Hosting.Fakes;
|
|
using Microsoft.AspNet.Hosting.Internal;
|
|
using Microsoft.AspNet.Hosting.Server;
|
|
using Microsoft.AspNet.Hosting.Startup;
|
|
using Microsoft.AspNet.Http;
|
|
using Microsoft.AspNet.Http.Features;
|
|
using Microsoft.AspNet.Http.Features.Internal;
|
|
using Microsoft.AspNet.Server.Features;
|
|
using Microsoft.AspNet.Testing.xunit;
|
|
using Microsoft.Dnx.Runtime.Infrastructure;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Internal;
|
|
using Microsoft.Extensions.OptionsModel;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNet.Hosting
|
|
{
|
|
public class HostingEngineTests : IServerFactory
|
|
{
|
|
private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
|
|
private IFeatureCollection _featuresSupportedByThisHost = new FeatureCollection();
|
|
|
|
[Fact]
|
|
public void HostingEngineThrowsWithNoServer()
|
|
{
|
|
var ex = Assert.Throws<InvalidOperationException>(() => CreateBuilder().Build().Start());
|
|
Assert.True(ex.Message.Contains("UseServer()"));
|
|
}
|
|
|
|
[Fact]
|
|
public void UseStartupThrowsWithNull()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() => CreateBuilder().UseStartup((string)null));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanStartWithOldServerConfig()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "server", "Microsoft.AspNet.Hosting.Tests" }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
var host = CreateBuilder(config).Build();
|
|
host.Start();
|
|
Assert.NotNull(host.ApplicationServices.GetRequiredService<IHostingEnvironment>());
|
|
}
|
|
|
|
[Fact]
|
|
public void CanStartWithServerConfig()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "Hosting:Server", "Microsoft.AspNet.Hosting.Tests" }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
var host = CreateBuilder(config).Build();
|
|
host.Start();
|
|
Assert.NotNull(host.ApplicationServices.GetRequiredService<IHostingEnvironment>());
|
|
}
|
|
|
|
[Fact]
|
|
public void CanSpecifyPortConfig()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "Hosting:Server", "Microsoft.AspNet.Hosting.Tests" },
|
|
{ "HTTP_PLATFORM_PORT", "abc123" }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
var host = CreateBuilder(config).Build();
|
|
var app = host.Start();
|
|
Assert.NotNull(host.ApplicationServices.GetRequiredService<IHostingEnvironment>());
|
|
Assert.Equal("http://localhost:abc123", app.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
|
}
|
|
|
|
[Fact]
|
|
public void CanDefaultAddresseIfNotConfigured()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "Hosting:Server", "Microsoft.AspNet.Hosting.Tests" }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
var host = CreateBuilder(config).Build();
|
|
var app = host.Start();
|
|
Assert.NotNull(host.ApplicationServices.GetRequiredService<IHostingEnvironment>());
|
|
Assert.Equal("http://localhost:5000", app.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngineCanBeStarted()
|
|
{
|
|
var engine = CreateBuilder()
|
|
.UseServer(this)
|
|
.UseStartup("Microsoft.AspNet.Hosting.Tests")
|
|
.Build()
|
|
.Start();
|
|
|
|
Assert.NotNull(engine);
|
|
Assert.Equal(1, _startInstances.Count);
|
|
Assert.Equal(0, _startInstances[0].DisposeCalls);
|
|
|
|
engine.Dispose();
|
|
|
|
Assert.Equal(1, _startInstances[0].DisposeCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngineDisposesServiceProvider()
|
|
{
|
|
var engine = CreateBuilder()
|
|
.UseServer(this)
|
|
.UseServices(s =>
|
|
{
|
|
s.AddTransient<IFakeService, FakeService>();
|
|
s.AddSingleton<IFakeSingletonService, FakeService>();
|
|
})
|
|
.UseStartup("Microsoft.AspNet.Hosting.Tests")
|
|
.Build()
|
|
.Start();
|
|
|
|
var singleton = (FakeService)engine.Services.GetService<IFakeSingletonService>();
|
|
var transient = (FakeService)engine.Services.GetService<IFakeService>();
|
|
|
|
Assert.False(singleton.Disposed);
|
|
Assert.False(transient.Disposed);
|
|
|
|
engine.Dispose();
|
|
|
|
Assert.True(singleton.Disposed);
|
|
Assert.True(transient.Disposed);
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngineNotifiesApplicationStarted()
|
|
{
|
|
var host = CreateBuilder()
|
|
.UseServer(this)
|
|
.Build();
|
|
var applicationLifetime = host.ApplicationServices.GetRequiredService<IApplicationLifetime>();
|
|
|
|
Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
|
using (host.Start())
|
|
{
|
|
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngineInjectsHostingEnvironment()
|
|
{
|
|
var engine = CreateBuilder()
|
|
.UseServer(this)
|
|
.UseStartup("Microsoft.AspNet.Hosting.Tests")
|
|
.UseEnvironment("WithHostingEnvironment")
|
|
.Build();
|
|
|
|
using (var server = engine.Start())
|
|
{
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
Assert.Equal("Changed", env.EnvironmentName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void CanReplaceStartupLoader()
|
|
{
|
|
var engine = CreateBuilder().UseServices(services => services.AddTransient<IStartupLoader, TestLoader>())
|
|
.UseServer(this)
|
|
.UseStartup("Microsoft.AspNet.Hosting.Tests")
|
|
.Build();
|
|
|
|
Assert.Throws<NotImplementedException>(() => engine.Start());
|
|
}
|
|
|
|
[Fact]
|
|
public void CanCreateApplicationServicesWithAddedServices()
|
|
{
|
|
var host = CreateBuilder().UseServices(services => services.AddOptions()).Build();
|
|
Assert.NotNull(host.ApplicationServices.GetRequiredService<IOptions<object>>());
|
|
}
|
|
|
|
[Fact]
|
|
public void EnvDefaultsToProductionIfNoConfig()
|
|
{
|
|
var engine = CreateBuilder().Build();
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnvDefaultsToConfigValueIfSpecifiedWithOldKey()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "ASPNET_ENV", "Staging" }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
|
|
var engine = CreateBuilder(config).Build();
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
Assert.Equal("Staging", env.EnvironmentName);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnvDefaultsToConfigValueIfSpecified()
|
|
{
|
|
var vals = new Dictionary<string, string>
|
|
{
|
|
{ "Hosting:Environment", "Staging" }
|
|
};
|
|
|
|
var builder = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(vals);
|
|
var config = builder.Build();
|
|
|
|
var engine = CreateBuilder(config).Build();
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
Assert.Equal("Staging", env.EnvironmentName);
|
|
}
|
|
|
|
[Fact]
|
|
public void WebRootCanBeResolvedFromTheProjectJson()
|
|
{
|
|
var engine = CreateBuilder().UseServer(this).Build();
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath);
|
|
Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists);
|
|
}
|
|
|
|
[Fact]
|
|
public void IsEnvironment_Extension_Is_Case_Insensitive()
|
|
{
|
|
var engine = CreateBuilder().UseServer(this).Build();
|
|
using (engine.Start())
|
|
{
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
Assert.True(env.IsEnvironment(EnvironmentName.Production));
|
|
Assert.True(env.IsEnvironment("producTion"));
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null, "")]
|
|
[InlineData("", "")]
|
|
[InlineData("/", "/")]
|
|
[InlineData(@"\", @"\")]
|
|
[InlineData("sub", "sub")]
|
|
[InlineData("sub/sub2/sub3", @"sub/sub2/sub3")]
|
|
public void MapPath_Facts(string virtualPath, string expectedSuffix)
|
|
{
|
|
RunMapPath(virtualPath, expectedSuffix);
|
|
}
|
|
|
|
[ConditionalTheory]
|
|
[OSSkipCondition(OperatingSystems.Linux)]
|
|
[OSSkipCondition(OperatingSystems.MacOSX)]
|
|
[InlineData(@"sub/sub2\sub3\", @"sub/sub2/sub3/")]
|
|
public void MapPath_Windows_Facts(string virtualPath, string expectedSuffix)
|
|
{
|
|
RunMapPath(virtualPath, expectedSuffix);
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngine_CreatesDefaultRequestIdentifierFeature_IfNotPresent()
|
|
{
|
|
// Arrange
|
|
HttpContext httpContext = null;
|
|
var requestDelegate = new RequestDelegate(innerHttpContext =>
|
|
{
|
|
httpContext = innerHttpContext;
|
|
return Task.FromResult(0);
|
|
});
|
|
var hostingEngine = CreateHostingEngine(requestDelegate);
|
|
|
|
// Act
|
|
var disposable = hostingEngine.Start();
|
|
|
|
// Assert
|
|
Assert.NotNull(httpContext);
|
|
Assert.IsType<FastHttpRequestIdentifierFeature>(httpContext.Features.Get<IHttpRequestIdentifierFeature>());
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngine_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent()
|
|
{
|
|
// Arrange
|
|
HttpContext httpContext = null;
|
|
var requestDelegate = new RequestDelegate(innerHttpContext =>
|
|
{
|
|
httpContext = innerHttpContext;
|
|
return Task.FromResult(0);
|
|
});
|
|
var requestIdentifierFeature = new Mock<IHttpRequestIdentifierFeature>().Object;
|
|
_featuresSupportedByThisHost[typeof(IHttpRequestIdentifierFeature)] = requestIdentifierFeature;
|
|
var hostingEngine = CreateHostingEngine(requestDelegate);
|
|
|
|
// Act
|
|
var disposable = hostingEngine.Start();
|
|
|
|
// Assert
|
|
Assert.NotNull(httpContext);
|
|
Assert.Same(requestIdentifierFeature, httpContext.Features.Get<IHttpRequestIdentifierFeature>());
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngine_InvokesConfigureMethodsOnlyOnce()
|
|
{
|
|
var engine = CreateBuilder()
|
|
.UseServer(this)
|
|
.UseStartup<CountStartup>()
|
|
.Build();
|
|
using (engine.Start())
|
|
{
|
|
var services = engine.ApplicationServices;
|
|
var services2 = engine.ApplicationServices;
|
|
Assert.Equal(1, CountStartup.ConfigureCount);
|
|
Assert.Equal(1, CountStartup.ConfigureServicesCount);
|
|
}
|
|
}
|
|
|
|
public class CountStartup
|
|
{
|
|
public static int ConfigureServicesCount;
|
|
public static int ConfigureCount;
|
|
|
|
public void ConfigureServices(IServiceCollection services)
|
|
{
|
|
ConfigureServicesCount++;
|
|
}
|
|
|
|
public void Configure(IApplicationBuilder app)
|
|
{
|
|
ConfigureCount++;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HostingEngine_ThrowsForBadConfigureServiceSignature()
|
|
{
|
|
var engine = CreateBuilder()
|
|
.UseServer(this)
|
|
.UseStartup<BadConfigureServicesStartup>()
|
|
.Build();
|
|
var ex = Assert.Throws<InvalidOperationException>(() => engine.Start());
|
|
Assert.True(ex.Message.Contains("ConfigureServices"));
|
|
}
|
|
|
|
public class BadConfigureServicesStartup
|
|
{
|
|
public void ConfigureServices(IServiceCollection services, int gunk) { }
|
|
public void Configure(IApplicationBuilder app) { }
|
|
}
|
|
|
|
private IHostingEngine CreateHostingEngine(RequestDelegate requestDelegate)
|
|
{
|
|
var host = CreateBuilder()
|
|
.UseServer(this)
|
|
.UseStartup(
|
|
appBuilder => { appBuilder.Run(requestDelegate); },
|
|
configureServices => configureServices.BuildServiceProvider());
|
|
return host.Build();
|
|
}
|
|
|
|
private void RunMapPath(string virtualPath, string expectedSuffix)
|
|
{
|
|
var engine = CreateBuilder().UseServer(this).Build();
|
|
|
|
using (engine.Start())
|
|
{
|
|
var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
|
var mappedPath = env.MapPath(virtualPath);
|
|
expectedSuffix = expectedSuffix.Replace('/', Path.DirectorySeparatorChar);
|
|
Assert.Equal(Path.Combine(env.WebRootPath, expectedSuffix), mappedPath);
|
|
}
|
|
}
|
|
|
|
private WebHostBuilder CreateBuilder(IConfiguration config = null)
|
|
{
|
|
return new WebHostBuilder(
|
|
CallContextServiceLocator.Locator.ServiceProvider,
|
|
config ?? new ConfigurationBuilder().Build());
|
|
}
|
|
|
|
public IFeatureCollection Initialize(IConfiguration configuration)
|
|
{
|
|
var features = new FeatureCollection();
|
|
features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
|
|
return features;
|
|
}
|
|
|
|
public IDisposable Start(IFeatureCollection serverFeatures, Func<IFeatureCollection, Task> application)
|
|
{
|
|
var startInstance = new StartInstance(application);
|
|
_startInstances.Add(startInstance);
|
|
application(_featuresSupportedByThisHost);
|
|
return startInstance;
|
|
}
|
|
|
|
public class StartInstance : IDisposable
|
|
{
|
|
private readonly Func<IFeatureCollection, Task> _application;
|
|
|
|
public StartInstance(Func<IFeatureCollection, Task> application)
|
|
{
|
|
_application = application;
|
|
}
|
|
|
|
public int DisposeCalls { get; set; }
|
|
|
|
public void Dispose()
|
|
{
|
|
DisposeCalls += 1;
|
|
}
|
|
}
|
|
|
|
private class TestLoader : IStartupLoader
|
|
{
|
|
public Type FindStartupType(string startupAssemblyName, IList<string> diagnosticMessages)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public StartupMethods LoadMethods(Type startupType, IList<string> diagnosticMessages)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private class ReadOnlyFeatureCollection : IFeatureCollection
|
|
{
|
|
public object this[Type key]
|
|
{
|
|
get { return null; }
|
|
set { throw new NotSupportedException(); }
|
|
}
|
|
|
|
public bool IsReadOnly
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public int Revision
|
|
{
|
|
get { return 0; }
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
private class ServerAddressesFeature : IServerAddressesFeature
|
|
{
|
|
public ICollection<string> Addresses { get; } = new List<string>();
|
|
}
|
|
}
|
|
}
|