Created
November 6, 2018 18:34
-
-
Save RianFuro/23dc1a8b89db05f1f1787599ffd55ebb to your computer and use it in GitHub Desktop.
IsolatedMap from AspNet.Hosting.Extensions module split up into Add...() and Use...() functions to facilitate starting HostedServices from the isolated containers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Builder; | |
using Microsoft.AspNetCore.Builder.Internal; | |
using Microsoft.AspNetCore.Hosting.Internal; | |
using Microsoft.AspNetCore.Http; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.DependencyInjection.Extensions; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.ObjectPool; | |
using IApplicationLifetime = Microsoft.AspNetCore.Hosting.IApplicationLifetime; | |
using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; | |
namespace ProperIsolatedMap | |
{ | |
public static class IsolatedHostingExtensions | |
{ | |
public static IServiceCollection AddIsolatedApplication<TStartup>(this IServiceCollection serviceCollection, string path) | |
{ | |
serviceCollection.AddSingleton(provider => | |
{ | |
var environment = provider.GetRequiredService<IHostingEnvironment>(); | |
var methods = StartupLoader.LoadMethods(provider, typeof(TStartup), environment.EnvironmentName); | |
return new IsolatedApplication(path, methods.ConfigureDelegate, methods.ConfigureServicesDelegate, provider); | |
}); | |
// TODO: this is not working when hosted service have been added before | |
serviceCollection.TryAddSingleton<IHostedService, IsolatedApplicationHostedService>(); | |
return serviceCollection; | |
} | |
public static IApplicationBuilder UseIsolatedApplications(this IApplicationBuilder applicationBuilder) | |
{ | |
var parts = applicationBuilder.ApplicationServices.GetServices<IsolatedApplication>(); | |
parts.ToList().ForEach(part => | |
{ | |
part.ApplicationBuilder.Use(async (context, next) => | |
{ | |
var factory = part.ApplicationBuilder.ApplicationServices | |
.GetRequiredService<IServiceScopeFactory>(); | |
context.Items[typeof(IServiceProvider)] = context.RequestServices; | |
try | |
{ | |
using (var scope = factory.CreateScope()) | |
{ | |
context.RequestServices = scope.ServiceProvider; | |
await next(); | |
} | |
} | |
finally | |
{ | |
context.RequestServices = null; | |
} | |
}); | |
applicationBuilder.Use(next => | |
{ | |
// Run the rest of the pipeline in the original context, | |
// with the services defined by the parent application builder. | |
part.ApplicationBuilder.Run(async context => | |
{ | |
var factory = applicationBuilder.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); | |
try | |
{ | |
using (var scope = factory.CreateScope()) | |
{ | |
context.RequestServices = scope.ServiceProvider; | |
await next(context); | |
} | |
} | |
finally | |
{ | |
context.RequestServices = null; | |
} | |
}); | |
var branch = part.ApplicationBuilder.Build(); | |
return context => branch(context); | |
}); | |
}); | |
return applicationBuilder; | |
} | |
} | |
internal class IsolatedApplication | |
{ | |
public string Path { get; } | |
public IApplicationBuilder ApplicationBuilder { get; } | |
public IsolatedApplication( | |
string path, | |
Action<IApplicationBuilder> configuration, | |
Func<IServiceCollection, IServiceProvider> registration, | |
IServiceProvider provider) | |
{ | |
Path = path; | |
var serviceCollection = CreateDefaultServiceCollection(provider); | |
ApplicationBuilder = new ApplicationBuilder(null) | |
{ | |
ApplicationServices = registration(serviceCollection) | |
}; | |
ApplicationBuilder.Map(Path, configuration); | |
} | |
private static ServiceCollection CreateDefaultServiceCollection(IServiceProvider provider) | |
{ | |
var services = new ServiceCollection(); | |
// Copy the services added by the hosting layer (WebHostBuilder.BuildHostingServices). | |
// See https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs. | |
services.AddLogging(); | |
if (provider.GetService<IHttpContextAccessor>() != null) | |
{ | |
services.AddSingleton(provider.GetService<IHttpContextAccessor>()); | |
} | |
services.AddSingleton(provider.GetRequiredService<IHostingEnvironment>()); | |
services.AddSingleton(provider.GetRequiredService<ILoggerFactory>()); | |
services.AddSingleton(provider.GetRequiredService<IApplicationLifetime>()); | |
services.AddSingleton(provider.GetRequiredService<IHttpContextFactory>()); | |
services.AddSingleton(provider.GetRequiredService<DiagnosticSource>()); | |
services.AddSingleton(provider.GetRequiredService<DiagnosticListener>()); | |
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); | |
return services; | |
} | |
} | |
internal class IsolatedApplicationHostedService : IHostedService | |
{ | |
private readonly IEnumerable<IsolatedApplication> _isolatedApplications; | |
public IsolatedApplicationHostedService(IEnumerable<IsolatedApplication> isolatedApplications) | |
{ | |
_isolatedApplications = isolatedApplications; | |
} | |
public Task StartAsync(CancellationToken cancellationToken) => | |
ForAllIsolatedApplicationHostedServices(service => service.StartAsync(cancellationToken)); | |
public Task StopAsync(CancellationToken cancellationToken) => | |
ForAllIsolatedApplicationHostedServices(service => service.StopAsync(cancellationToken)); | |
private Task ForAllIsolatedApplicationHostedServices(Func<IHostedService, Task> func) => Task.WhenAll( | |
_isolatedApplications.Select(app => | |
Task.WhenAll(app.ApplicationBuilder.ApplicationServices.GetServices<IHostedService>() | |
.Select(func))) | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace RootApp | |
{ | |
public class Startup | |
{ | |
// This method gets called by the runtime. Use this method to add services to the container. | |
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddIsolatedApplication<WebApp2.Startup>("/sub"); | |
services.AddHostedService<AHostedService>(); | |
} | |
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
app.UseDeveloperExceptionPage(); | |
} | |
app.UseIsolatedApplications(); | |
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); | |
} | |
} | |
class AHostedService : IHostedService | |
{ | |
public Task StartAsync(CancellationToken cancellationToken) | |
{ | |
Console.WriteLine("Starting hosted service from main app."); | |
return Task.CompletedTask; | |
} | |
public Task StopAsync(CancellationToken cancellationToken) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace SubApp | |
{ | |
public class Startup | |
{ | |
// This method gets called by the runtime. Use this method to add services to the container. | |
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddHostedService<SomeHostedService>(); | |
} | |
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
app.UseDeveloperExceptionPage(); | |
} | |
app.Run(async (context) => { await context.Response.WriteAsync("Hello Startup2 World!"); }); | |
} | |
} | |
class SomeHostedService : IHostedService | |
{ | |
public Task StartAsync(CancellationToken cancellationToken) | |
{ | |
Console.WriteLine("Started some hosted service from subapp"); | |
return Task.CompletedTask; | |
} | |
public Task StopAsync(CancellationToken cancellationToken) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment