-
-
Save davidfowl/2ae62e7c34c27a58faacf8b0463b1586 to your computer and use it in GitHub Desktop.
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 Microsoft.AspNetCore.Hosting.Server; | |
using Microsoft.AspNetCore.Hosting.Server.Features; | |
using Microsoft.AspNetCore.Http.Features; | |
public static class ServiceCollectionExtensions | |
{ | |
public static IServiceCollection AddCompositeServer(this IServiceCollection services) | |
{ | |
// HACK: We're removing existing IServer implementations from the DI container and | |
// resolving them by implementation type. This allows us to build a composite server that starts all registered | |
// IServer implementations. | |
var descriptors = services.Where(x => x.Lifetime == ServiceLifetime.Singleton && x.ServiceType == typeof(IServer)).ToArray(); | |
// Remove the IServer descriptors but add it back as the implementation type. | |
var serverTypes = new HashSet<Type>(); | |
foreach (var d in descriptors) | |
{ | |
services.Remove(d); | |
// Remove dupes | |
serverTypes.Add(d.ImplementationType!); | |
} | |
foreach (var serverType in serverTypes) | |
{ | |
services.AddSingleton(serverType); | |
} | |
// Add the new composite server and resolves the existing servers by implementation type (that's what allows them to co-exist in the DI Container) | |
services.AddSingleton<IServer>(s => new CompositeServer(serverTypes.Select(t => (IServer)s.GetRequiredService(t)).ToArray())); | |
return services; | |
} | |
private class CompositeServer : IServer | |
{ | |
private readonly IServer[] _servers; | |
private readonly ServerAddressesFeature _serverAddressesFeature; | |
public CompositeServer(IServer[] servers) | |
{ | |
_servers = servers ?? throw new ArgumentNullException(nameof(servers)); | |
var features = new FeatureCollection(); | |
// This will overwrite features based on registration order | |
foreach (var s in servers) | |
{ | |
foreach (var f in s.Features) | |
{ | |
features[f.Key] = f.Value; | |
} | |
} | |
// We need to combine all server addresses into a single feature | |
_serverAddressesFeature = new ServerAddressesFeature(); | |
// Overwrite the server addresses feature | |
features.Set<IServerAddressesFeature>(_serverAddressesFeature); | |
Features = features; | |
} | |
public IFeatureCollection Features { get; } | |
public void Dispose() | |
{ | |
foreach (var server in _servers) | |
{ | |
server.Dispose(); | |
} | |
} | |
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull | |
{ | |
foreach (var server in _servers) | |
{ | |
await server.StartAsync(application, cancellationToken); | |
// Server addresses are populated after starting, now update | |
// the list of addresses | |
if (server.Features.Get<IServerAddressesFeature>() is { } feature) | |
{ | |
foreach (var addresses in feature.Addresses) | |
{ | |
_serverAddressesFeature.Add(addresses); | |
} | |
} | |
} | |
} | |
public async Task StopAsync(CancellationToken cancellationToken) | |
{ | |
foreach (var server in _servers) | |
{ | |
await server.StopAsync(cancellationToken); | |
} | |
} | |
private class ServerAddressesFeature : IServerAddressesFeature | |
{ | |
private readonly List<string> _addresses = new List<string>(); | |
public ICollection<string> Addresses { get; } | |
public bool PreferHostingUrls { get; set; } | |
public ServerAddressesFeature() | |
{ | |
Addresses = _addresses.AsReadOnly(); | |
} | |
public void Add(string address) | |
{ | |
_addresses.Add(address); | |
} | |
} | |
} | |
} |
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
var builder = WebApplication.CreateBuilder(args); | |
builder.WebHost.UseHttpSys(o => | |
{ | |
o.UrlPrefixes.Add("http://localhost:8080"); | |
}); | |
// Kestrel is already configured by default, so tweak the url to avoid port conflict | |
builder.WebHost.ConfigureKestrel(o => | |
{ | |
o.ListenLocalhost(8081); | |
}); | |
// The order of this is important. It must be done after the IServer implementations have been configured | |
builder.Services.AddCompositeServer(); | |
var app = builder.Build(); | |
app.MapGet("/", (HttpContext context) => context.Features.ToString()); | |
app.Run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment