Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Allow external container to resolve IStartup #1321

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ private void EnsureApplicationServices()
{
if (_applicationServices == null)
{
_applicationServices = _hostingServiceProvider.GetServices<IServiceProvider>()
.LastOrDefault();
EnsureStartup();

_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
}
}
Expand All @@ -188,7 +191,7 @@ private void EnsureStartup()
return;
}

_startup = _hostingServiceProvider.GetService<IStartup>();
_startup = (_applicationServices ?? _hostingServiceProvider).GetService<IStartup>();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if resolved IStartup instance should be registered with _hostingServiceProvider as well. If it does, let me know. I'll add it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you resolving the IStartup from the application services? That's just broken. The hosting service provider is responsible for building the IStartup. There are 2 separate phases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I need to resolve it from Unity when it is provided as instance. Did you run the test I submitted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point in time _applicationServices holds reference to externally supplied IServiceProvider or null in which case normal flow kicks in and IStartup is resolved from _hostingServiceProvider.


if (_startup == null)
{
Expand Down
80 changes: 79 additions & 1 deletion test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void Configure(IApplicationBuilder app) =>
app.Use((ctx, next) => ctx.Response.WriteAsync(
$"{ctx.RequestServices.GetRequiredService<SimpleService>().Message}, {ctx.RequestServices.GetRequiredService<TestService>().Message}"));
}

public class ThirdPartyContainer
{
public IServiceCollection Services { get; set; }
Expand All @@ -83,6 +83,84 @@ public class ThirdPartyContainerServiceProviderFactory : IServiceProviderFactory
public IServiceProvider CreateServiceProvider(ThirdPartyContainer containerBuilder) => containerBuilder.Services.BuildServiceProvider();
}

[Fact]
public async Task ExternalContainerInstanceCanBeUsedToGetIStartup()
{
var external = new UnityContainer();
external.Register(typeof(TestService)); // Register outside builder service

var builder = new WebHostBuilder()
.ConfigureTestServices(services => // Register inside builder service
services.AddSingleton(new SimpleService { Message = "OverridesConfigureServices" }))
.UseStartup<UnityStartup>()
.ConfigureServices(s => { // Register outside provider instance
s.AddSingleton<IServiceProvider>(external.BuildServiceProvider(s));
});

var host = new TestServer(builder);

var response = await host.CreateClient().GetStringAsync("/");

Assert.Equal("ConfigureServices, UnityStartupConstructor", response);
}

public class UnityContainer
{
internal TestServiceCollection Registrations { get; set; } = new TestServiceCollection();


public void Register(Type registeredType)
{
Registrations.AddTransient(registeredType);
}

public IServiceProvider BuildServiceProvider(IServiceCollection services)
{
var collection = new TestServiceCollection();

foreach (var service in services) collection.Add(service);
foreach (var service in Registrations) collection.Add(service);
collection.AddSingleton(this);

return collection.BuildServiceProvider();
}

internal class TestServiceCollection : System.Collections.Generic.List<ServiceDescriptor>,
IServiceCollection
{
}
}

public class UnityStartup : IStartup
{
UnityContainer _container;
TestService _outside;

public UnityStartup(UnityContainer container, TestService outside)
{
_container = container ?? throw new ArgumentNullException(nameof(container));
_outside = outside ?? throw new ArgumentNullException(nameof(outside));

_container.Registrations.AddSingleton(new TestService { Message = "UnityStartupConstructor" });
}

public void Configure(IApplicationBuilder app) =>
app.Use((ctx, next) => ctx.Response.WriteAsync(
$"{ctx.RequestServices.GetRequiredService<SimpleService>().Message}, " +
$"{ctx.RequestServices.GetRequiredService<TestService>().Message}"));

public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new SimpleService { Message = "ConfigureServices" });

// Normally container would register more types from colection
// but for this test will just create updated provider
foreach (var service in _container.Registrations) services.Add(service);

return services.BuildServiceProvider();
}
}

[Fact]
public void CaptureStartupErrorsSettingPreserved()
{
Expand Down