// 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.Generic; using System.IO; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Testing.Internal; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.Testing { /// /// Builder API for bootstraping an MVC application for functional tests. /// /// The application startup class. public class MvcWebApplicationBuilder where TStartup : class { public string ContentRoot { get; set; } public IList> ConfigureServicesBeforeStartup { get; set; } = new List>(); public IList> ConfigureServicesAfterStartup { get; set; } = new List>(); public List ApplicationAssemblies { get; set; } = new List(); /// /// Configures services before TStartup.ConfigureServices runs. /// /// The to configure the services with. /// An instance of this public MvcWebApplicationBuilder ConfigureBeforeStartup(Action configure) { ConfigureServicesBeforeStartup.Add(configure); return this; } /// /// Configures services after TStartup.ConfigureServices runs. /// /// The to configure the services with. /// An instance of this public MvcWebApplicationBuilder ConfigureAfterStartup(Action configure) { ConfigureServicesAfterStartup.Add(configure); return this; } /// /// Configures to include the default set /// of provided by . /// /// An instance of this public MvcWebApplicationBuilder UseApplicationAssemblies() { var depsFileName = $"{typeof(TStartup).Assembly.GetName().Name}.deps.json"; var depsFile = new FileInfo(Path.Combine(AppContext.BaseDirectory, depsFileName)); if (!depsFile.Exists) { throw new InvalidOperationException($"Can't find'{depsFile.FullName}'. This file is required for functional tests " + "to run properly. There should be a copy of the file on your source project bin folder. If thats not the " + "case, make sure that the property PreserveCompilationContext is set to true on your project file. E.g" + "'true'." + $"For functional tests to work they need to either run from the build output folder or the {Path.GetFileName(depsFile.FullName)} " + $"file from your application's output directory must be copied" + "to the folder where the tests are running on. A common cause for this error is having shadow copying enabled when the " + "tests run."); } ApplicationAssemblies.AddRange(DefaultAssemblyPartDiscoveryProvider .DiscoverAssemblyParts(typeof(TStartup).Assembly.GetName().Name) .Select(s => ((AssemblyPart)s).Assembly) .ToList()); return this; } /// /// Configures the application content root. /// /// The glob pattern to use for finding the solution. /// The relative path to the content root from the solution file. /// An instance of this public MvcWebApplicationBuilder UseSolutionRelativeContentRoot( 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) { ContentRoot = Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)); return this; } directoryInfo = directoryInfo.Parent; } while (directoryInfo.Parent != null); throw new Exception($"Solution root could not be located using application root {applicationBasePath}."); } public TestServer Build() { var builder = new WebHostBuilder() .UseStartup>() // This is necessary so that IHostingEnvironment.ApplicationName has the right // value and libraries depending on it (to load the dependency context, for example) // work properly. .UseSetting(WebHostDefaults.ApplicationKey, typeof(TStartup).Assembly.GetName().Name) .UseContentRoot(ContentRoot) .ConfigureServices(InitializeServices); return new TestServer(builder); } protected virtual void InitializeServices(IServiceCollection services) { // Inject a custom application part manager. Overrides AddMvcCore() because that uses TryAdd(). var manager = new ApplicationPartManager(); foreach (var assembly in ApplicationAssemblies) { manager.ApplicationParts.Add(new AssemblyPart(assembly)); } services.AddSingleton(manager); services.AddSingleton(new TestServiceRegistrations { Before = ConfigureServicesBeforeStartup, After = ConfigureServicesAfterStartup }); } } }