aspnet / Hosting

[Archived] Code for hosting and starting up an ASP.NET Core application. Project moved to https://github.com/aspnet/Extensions and https://github.com/aspnet/AspNetCore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Multiple IServer implementations in the same app

aL3891 opened this issue · comments

Hello folks,
I'm sorry if this is a noob question, but i'm looking at ways to make an asp.net core application available both over regular http as well as via a custom server. i'd like both servers to pipe into the "same" application so that they share di and any other state that comes with the host if possible.

Basically I want to be able to process requests coming in both over http and from somewhere else, (in my case in memory but could be from anywhere)

As far as I understand, the best way to do this is to implement IServer on my own custom server and do a UseServer() with that, but since I also want to have kestrel listen to requests, I've been thinking I should make a wrapper IServer implementation that starts both my server and kestrel.

Is this line of thinking correct or is there a better/ built in way to do this? as far as I understand there can only be one IServer implementation registered

You're right on all counts.

Don't worry about using UseServer, it takes an instance. Directly registering with DI will make it easier for you to resolve services.

Great :)
Is there anything else to think about with the move to generic host in .net core 3? Following some prs and digging though the code I've gathered that a web app is just a HostedService in the new model(?), is it still the case that IServer is a Singleton or is it scoped?

(Or actually, maybe you can already register IServer as scoped today and just create a scope for each server? I don't remember if you can override registrations in a child scope)

No, IServer is still a singleton in the new generic host scenario.

Cool np, thanks for the info!
I ended up with this as a poc, perhaps it will be of use to someone :)

public class Program
{
    public static Task Main(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
        .UseKestrel()
        .UseMultiServer<KestrelServer, TestServer>()
        .UseStartup<Startup>()
        .Build()
        .RunAsync();
}

public class MultiServer<TServer1, TServer2> : MultiServer, IServer where TServer1 : IServer where TServer2 : IServer
{
    public MultiServer(TServer1 server1, TServer2 server2) : base(new IServer[] { server1, server2 }) { }
}

public class MultiServer : IServer
{
    public IServer[] Servers { get; }
    public IFeatureCollection Features { get; set; } = new FeatureCollection();

    public MultiServer(IServer[] servers)
    {
        Servers = servers;
        foreach (var f in servers.SelectMany(s => s.Features))
            Features[f.Key] = f.Value;
    }

    public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) =>
        Task.WhenAll(Servers.Select(s => s.StartAsync(application, cancellationToken)));

    public Task StopAsync(CancellationToken cancellationToken) =>
        Task.WhenAll(Servers.Select(s => s.StopAsync(cancellationToken)));

    public void Dispose()
    {
        foreach (var s in Servers)
            s.Dispose();
    }
}

public static class ServerExtensions
{
    public static IWebHostBuilder UseMultiServer<TServer1, TServer2>(this IWebHostBuilder hostBuilder) where TServer1 : class, IServer where TServer2 : class, IServer =>
        hostBuilder.ConfigureServices(services =>
        {
            services.AddSingleton<TServer1, TServer1>();
            services.AddSingleton<TServer2, TServer2>();
            services.AddSingleton<IServer, MultiServer<TServer1, TServer2>>();
        });
}

public class TestServer : IServer
{
    public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) => Task.CompletedTask;
    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    public IFeatureCollection Features { get; } = new FeatureCollection();
    public void Dispose() { }
}