Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save divyang4481/d3152cc4b2d792c9e161c493d36606ce to your computer and use it in GitHub Desktop.
Save divyang4481/d3152cc4b2d792c9e161c493d36606ce 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.
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)))
);
}
}
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();
}
}
}
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