Add extension methods in our test host package to streamline setup of apps.

* Create a sources package to encode the convention followed in our templates to create a WebHost.
* Add an extension method to setup the content root relative to the solution folder.
* Add a factory method to create a WebHostBuilder based on the pattern followed in our template.
This commit is contained in:
Javier Calvarro Nelson 2017-09-08 18:06:37 -07:00
parent 1c3fa82908
commit c24c717eee
18 changed files with 414 additions and 6 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.10
VisualStudioVersion = 15.0.26913.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0497F39-AFFB-4819-A116-E39E361915AB}"
ProjectSection(SolutionItems) = preProject
@ -63,6 +63,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenericWebHost", "samples\G
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Hosting.Tests", "test\Microsoft.Extensions.Hosting.Tests\Microsoft.Extensions.Hosting.Tests.csproj", "{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests", "test\Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests\Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests.csproj", "{3834E274-3AF5-4751-8857-5B67BBE607DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildWebHostPatternTestSite", "test\TestAssets\BuildWebHostPatternTestSite\BuildWebHostPatternTestSite.csproj", "{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -293,6 +297,30 @@ Global
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|x86.ActiveCfg = Release|Any CPU
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3}.Release|x86.Build.0 = Release|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Debug|x86.ActiveCfg = Debug|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Debug|x86.Build.0 = Debug|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Release|Any CPU.Build.0 = Release|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Release|x86.ActiveCfg = Release|Any CPU
{3834E274-3AF5-4751-8857-5B67BBE607DD}.Release|x86.Build.0 = Release|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Debug|x86.ActiveCfg = Debug|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Debug|x86.Build.0 = Debug|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Release|Any CPU.Build.0 = Release|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Release|x86.ActiveCfg = Release|Any CPU
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -312,12 +340,14 @@ Global
{AB0B7394-278D-4838-A59C-276ED88D00CC} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{FA7D2012-C1B4-4AF7-9ADD-381B2004EA16} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{EDFF02F0-A8A4-4EB1-A179-94D7500FB266} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
{58194285-5891-464A-A96B-0FE043029E8A} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
{58194285-5891-464A-A96B-0FE043029E8A} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{F894D8C5-B760-4734-AD31-3CA6FC557CCF} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
{8529E5F7-059F-4A06-AD6E-ECDF4F4838FE} = {9C7520A0-F2EB-411C-8BB2-80B39C937217}
{1DA77D55-5DB9-4426-87DC-758579335944} = {E0497F39-AFFB-4819-A116-E39E361915AB}
{FCDD1C82-623C-4779-8A9C-B0D827CEA8BF} = {9C7520A0-F2EB-411C-8BB2-80B39C937217}
{45E296BB-7628-49AF-B5A5-04CD9A89CAD3} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{3834E274-3AF5-4751-8857-5B67BBE607DD} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{37C4BD55-CD39-4C4E-BA01-0AEAFCE128F1} = {FA7D2012-C1B4-4AF7-9ADD-381B2004EA16}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AABD536D-E05F-409B-A716-535E0C478076}

View File

@ -5,6 +5,7 @@
],
"packages": {
"Microsoft.AspNetCore.Certificates.Configuration.Sources": {},
"Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources": {},
"Microsoft.AspNetCore.Server.IntegrationTesting": {}
}
},

View File

@ -0,0 +1,50 @@
// 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;
namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory
{
internal class FactoryResolutionResult
{
public FactoryResolutionResultKind ResultKind { get; set; }
public Type ProgramType { get; set; }
public Func<string[], IWebHost> WebHostFactory { get; set; }
public Func<string[], IWebHostBuilder> WebHostBuilderFactory { get; set; }
internal static FactoryResolutionResult NoBuildWebHost(Type programType) =>
new FactoryResolutionResult
{
ProgramType = programType,
ResultKind = FactoryResolutionResultKind.NoBuildWebHost
};
internal static FactoryResolutionResult NoCreateWebHostBuilder(Type programType) =>
new FactoryResolutionResult
{
ProgramType = programType,
ResultKind = FactoryResolutionResultKind.NoCreateWebHostBuilder
};
internal static FactoryResolutionResult NoEntryPoint() =>
new FactoryResolutionResult
{
ResultKind = FactoryResolutionResultKind.NoEntryPoint
};
internal static FactoryResolutionResult Succeded(Func<string[], IWebHost> factory, Type programType) => new FactoryResolutionResult
{
ProgramType = programType,
ResultKind = FactoryResolutionResultKind.Success,
WebHostFactory = factory
};
internal static FactoryResolutionResult Succeded(Func<string[], IWebHostBuilder> factory, Type programType) => new FactoryResolutionResult
{
ProgramType = programType,
ResultKind = FactoryResolutionResultKind.Success,
WebHostBuilderFactory = factory,
WebHostFactory = args => factory(args).Build()
};
}
}

View File

@ -0,0 +1,14 @@
// 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.WebHostBuilderFactory
{
internal enum FactoryResolutionResultKind
{
Success,
NoEntryPoint,
NoCreateWebHostBuilder,
NoBuildWebHost
}
}

View File

@ -0,0 +1,62 @@
// 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.Reflection;
namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory
{
internal class WebHostFactoryResolver
{
public static readonly string CreateWebHostBuilder = nameof(CreateWebHostBuilder);
public static readonly string BuildWebHost = nameof(BuildWebHost);
public static FactoryResolutionResult ResolveWebHostBuilderFactory(Assembly assembly)
{
var programType = assembly?.EntryPoint?.DeclaringType;
if (programType == null)
{
return FactoryResolutionResult.NoEntryPoint();
}
var factory = programType?.GetTypeInfo().GetDeclaredMethod(CreateWebHostBuilder);
if (factory == null)
{
return FactoryResolutionResult.NoCreateWebHostBuilder(programType);
}
return FactoryResolutionResult.Succeded(args => (IWebHostBuilder)factory.Invoke(null, new object[] { args }), programType);
}
public static FactoryResolutionResult ResolveWebHostFactory(Assembly assembly)
{
// We want to give priority to BuildWebHost over CreateWebHostBuilder for backwards
// compatibility with existing projects that follow the old pattern.
var findResult = ResolveWebHostBuilderFactory(assembly);
switch (findResult.ResultKind)
{
case FactoryResolutionResultKind.NoEntryPoint:
return findResult;
case FactoryResolutionResultKind.Success:
case FactoryResolutionResultKind.NoCreateWebHostBuilder:
var buildWebHostMethod = findResult.ProgramType.GetTypeInfo().GetDeclaredMethod("BuildWebHost");
if (buildWebHostMethod == null)
{
if (findResult.ResultKind == FactoryResolutionResultKind.Success)
{
return findResult;
}
return FactoryResolutionResult.NoBuildWebHost(findResult.ProgramType);
}
else
{
return FactoryResolutionResult.Succeded(args => (IWebHost)buildWebHostMethod.Invoke(null, new object[] { args }), findResult.ProgramType);
}
case FactoryResolutionResultKind.NoBuildWebHost:
default:
throw new InvalidOperationException();
}
}
}
}

View File

@ -8,6 +8,12 @@
<PackageTags>aspnetcore;hosting;testing</PackageTags>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\shared\Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources\FactoryResolutionResult.cs" />
<Compile Include="..\..\shared\Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources\FactoryResolutionResultKind.cs" />
<Compile Include="..\..\shared\Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources\WebHostFactoryResolver.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.AspNetCore.Hosting\Microsoft.AspNetCore.Hosting.csproj" />
</ItemGroup>

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
@ -47,6 +49,35 @@ namespace Microsoft.AspNetCore.TestHost
return webHostBuilder;
}
public static IWebHostBuilder UseSolutionRelativeContentRoot(
this IWebHostBuilder builder,
string solutionRelativePath,
string solutionName = "*.sln")
{
if (solutionRelativePath == null)
{
throw new ArgumentNullException(nameof(solutionRelativePath));
}
var applicationBasePath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
if (solutionPath != null)
{
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
return builder;
}
directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null);
throw new InvalidOperationException($"Solution root could not be located using application root {applicationBasePath}.");
}
private class ConfigureTestServicesStartupConfigureServicesFilter : IStartupConfigureServicesFilter
{
private readonly Action<IServiceCollection> _servicesConfiguration;

View File

@ -0,0 +1,26 @@
// 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.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.WebHostBuilderFactory;
namespace Microsoft.AspNetCore.TestHost
{
public static class WebHostBuilderFactory
{
public static IWebHostBuilder CreateFromAssemblyEntryPoint(Assembly assembly, string [] args)
{
var result = WebHostFactoryResolver.ResolveWebHostBuilderFactory(assembly);
if (result.ResultKind != FactoryResolutionResultKind.Success)
{
return null;
}
return result.WebHostBuilderFactory(args);
}
public static IWebHostBuilder CreateFromTypesAssemblyEntryPoint<T>(string[] args) =>
CreateFromAssemblyEntryPoint(typeof(T).Assembly, args);
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\shared\Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources\**\*.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Hosting.Abstractions\Microsoft.AspNetCore.Hosting.Abstractions.csproj" />
<ProjectReference Include="..\TestAssets\BuildWebHostPatternTestSite\BuildWebHostPatternTestSite.csproj" />
<ProjectReference Include="..\TestAssets\IStartupInjectionAssemblyName\IStartupInjectionAssemblyName.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,60 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests
{
public class WebHostFactoryResolverTests
{
[Fact]
public void CanFindWebHostBuilder_CreateWebHostBuilderPattern()
{
// Arrange & Act
var resolverResult = WebHostFactoryResolver.ResolveWebHostBuilderFactory(typeof(IStartupInjectionAssemblyName.Startup).Assembly);
// Assert
Assert.Equal(FactoryResolutionResultKind.Success, resolverResult.ResultKind);
Assert.NotNull(resolverResult.WebHostBuilderFactory);
Assert.NotNull(resolverResult.WebHostFactory);
Assert.IsAssignableFrom<IWebHostBuilder>(resolverResult.WebHostBuilderFactory(Array.Empty<string>()));
}
[Fact]
public void CanFindWebHost_CreateWebHostBuilderPattern()
{
// Arrange & Act
var resolverResult = WebHostFactoryResolver.ResolveWebHostFactory(typeof(IStartupInjectionAssemblyName.Startup).Assembly);
// Assert
Assert.Equal(FactoryResolutionResultKind.Success, resolverResult.ResultKind);
Assert.NotNull(resolverResult.WebHostBuilderFactory);
Assert.NotNull(resolverResult.WebHostFactory);
}
[Fact]
public void CanNotFindWebHostBuilder_BuildWebHostPattern()
{
// Arrange & Act
var resolverResult = WebHostFactoryResolver.ResolveWebHostBuilderFactory(typeof(BuildWebHostPatternTestSite.Startup).Assembly);
// Assert
Assert.Equal(FactoryResolutionResultKind.NoCreateWebHostBuilder, resolverResult.ResultKind);
Assert.Null(resolverResult.WebHostBuilderFactory);
Assert.Null(resolverResult.WebHostFactory);
}
[Fact]
public void CanFindWebHost_BuildWebHostPattern()
{
// Arrange & Act
var resolverResult = WebHostFactoryResolver.ResolveWebHostFactory(typeof(BuildWebHostPatternTestSite.Startup).Assembly);
// Assert
Assert.Equal(FactoryResolutionResultKind.Success, resolverResult.ResultKind);
Assert.Null(resolverResult.WebHostBuilderFactory);
Assert.NotNull(resolverResult.WebHostFactory);
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Hosting\Microsoft.AspNetCore.Hosting.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.TestHost\Microsoft.AspNetCore.TestHost.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
// 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 Microsoft.AspNetCore.Hosting;
namespace BuildWebHostPatternTestSite
{
class Program
{
static void Main(string[] args)
{
}
public static IWebHost BuildWebHost(string[] args) => null;
}
}

View File

@ -0,0 +1,19 @@
// 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 Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace BuildWebHostPatternTestSite
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder builder)
{
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>

View File

@ -13,8 +13,7 @@ namespace IStartupInjectionAssemblyName
{
public static void Main(string[] args)
{
var server = new TestServer(new WebHostBuilder()
.ConfigureServices(services => services.AddSingleton<IStartup, Startup>()));
var server = new TestServer(CreateWebHostBuilder(args));
// Mimic application startup messages so application deployer knows that the application has started
Console.WriteLine("Application started. Press Ctrl+C to shut down.");
@ -22,5 +21,9 @@ namespace IStartupInjectionAssemblyName
Task.Run(async () => Console.WriteLine(await server.CreateClient().GetStringAsync(""))).GetAwaiter().GetResult();
}
// Do not change the signature of this method. It's used for tests.
private static IWebHostBuilder CreateWebHostBuilder(string [] args) =>
new WebHostBuilder().ConfigureServices(services => services.AddSingleton<IStartup, Startup>());
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50801/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IStartupInjectionAssemblyName": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:50802/"
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50800/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Microsoft.AspNetCore.Hosting.TestSites": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:50803/"
}
}
}