public GenericGameHostBuilder(IHostBuilder builder) { _builder = builder; _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "GAMENETCORE_") .Build(); _builder.ConfigureHostConfiguration(config => { config.AddConfiguration(_config); // We do this super early but still late enough that we can process the configuration // wired up by calls to UseSetting ExecuteHostingStartups(); }); // IHostingStartup needs to be executed before any direct methods on the builder // so register these callbacks first _builder.ConfigureAppConfiguration((context, configurationBuilder) => { if (_hostingStartupGameHostBuilder != null) { var gamehostContext = GetGameHostBuilderContext(context); _hostingStartupGameHostBuilder.ConfigureAppConfiguration(gamehostContext, configurationBuilder); } }); _builder.ConfigureServices((context, services) => { if (_hostingStartupGameHostBuilder != null) { var gamehostContext = GetGameHostBuilderContext(context); _hostingStartupGameHostBuilder.ConfigureServices(gamehostContext, services); } }); _builder.ConfigureServices((context, services) => { var gamehostContext = GetGameHostBuilderContext(context); var gameHostOptions = (GameHostOptions)context.Properties[typeof(GameHostOptions)]; // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting services.AddSingleton(gamehostContext.HostingEnvironment); #pragma warning disable CS0618 // Type or member is obsolete services.AddSingleton((GameNetCore.Hosting.IHostingEnvironment)gamehostContext.HostingEnvironment); services.AddSingleton <IApplicationLifetime, GenericGameHostApplicationLifetime>(); #pragma warning restore CS0618 // Type or member is obsolete services.Configure <GenericGameHostServiceOptions>(options => { // Set the options options.GameHostOptions = gameHostOptions; // Store and forward any startup errors options.HostingStartupExceptions = _hostingStartupErrors; }); services.AddHostedService <GenericGameHostService>(); // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up // We need to flow this differently var listener = new DiagnosticListener("Contoso.GameNetCore"); services.TryAddSingleton <DiagnosticListener>(listener); services.TryAddSingleton <DiagnosticSource>(listener); services.TryAddSingleton <IProtoContextFactory, DefaultProtoContextFactory>(); services.TryAddScoped <IMiddlewareFactory, MiddlewareFactory>(); services.TryAddSingleton <IApplicationBuilderFactory, ApplicationBuilderFactory>(); // Support UseStartup(assemblyName) if (!string.IsNullOrEmpty(gameHostOptions.StartupAssembly)) { try { var startupType = StartupLoader.FindStartupType(gameHostOptions.StartupAssembly, gamehostContext.HostingEnvironment.EnvironmentName); UseStartup(startupType, context, services); } catch (Exception ex) when(gameHostOptions.CaptureStartupErrors) { var capture = ExceptionDispatchInfo.Capture(ex); services.Configure <GenericGameHostServiceOptions>(options => { options.ConfigureApplication = app => { // Throw if there was any errors initializing startup capture.Throw(); }; }); } } }); }
void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services) { var gameHostBuilderContext = GetGameHostBuilderContext(context); var gameHostOptions = (GameHostOptions)context.Properties[typeof(GameHostOptions)]; ExceptionDispatchInfo startupError = null; object instance = null; ConfigureBuilder configureBuilder = null; try { // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose if (typeof(IStartup).IsAssignableFrom(startupType)) { throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); } if (StartupLoader.HasConfigureServicesIServiceProviderDelegate(startupType, context.HostingEnvironment.EnvironmentName)) { throw new NotSupportedException($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported."); } instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(gameHostBuilderContext), startupType); context.Properties[_startupKey] = instance; // Startup.ConfigureServices var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName); var configureServices = configureServicesBuilder.Build(instance); configureServices(services); // REVIEW: We're doing this in the callback so that we have access to the hosting environment // Startup.ConfigureContainer var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName); if (configureContainerBuilder.MethodInfo != null) { var containerType = configureContainerBuilder.GetContainerType(); // Store the builder in the property bag _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder; var actionType = typeof(Action <,>).MakeGenericType(typeof(HostBuilderContext), containerType); // Get the private ConfigureContainer method on this type then close over the container type var configureCallback = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance) .MakeGenericMethod(containerType) .CreateDelegate(actionType, this); // _builder.ConfigureContainer<T>(ConfigureContainer); typeof(IHostBuilder).GetMethods().First(m => m.Name == nameof(IHostBuilder.ConfigureContainer)) .MakeGenericMethod(containerType) .InvokeWithoutWrappingExceptions(_builder, new object[] { configureCallback }); } // Resolve Configure after calling ConfigureServices and ConfigureContainer configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName); } catch (Exception ex) when(gameHostOptions.CaptureStartupErrors) { startupError = ExceptionDispatchInfo.Capture(ex); } // Startup.Configure services.Configure <GenericGameHostServiceOptions>(options => { options.ConfigureApplication = app => { // Throw if there was any errors initializing startup startupError?.Throw(); // Execute Startup.Configure if (instance != null && configureBuilder != null) { configureBuilder.Build(instance)(app); } }; }); }