Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No service for type 'Microsoft.AspNetCore.Server.Kestrel.Core.IHttpsConfigurationService' after upgrading to .NET 8 preview #48956

Closed
1 task done
timmydo opened this issue Jun 22, 2023 · 10 comments
Assignees
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Milestone

Comments

@timmydo
Copy link

timmydo commented Jun 22, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Our existing application fails to startup:

System.InvalidOperationException: No service for type 'Microsoft.AspNetCore.Server.Kestrel.Core.IHttpsConfigurationService' has been registered.
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions.EnableHttpsConfiguration()
   at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions, Action`1 configureOptions)
   at SerpHost.Kestrel.KestrelServerWrapper.SerpKestrelServerOptionsSetup.SetListenOptions(ListenOptions options2, IKestrelConfig kestrelConfig, X509Certificate2 certificate, Dictionary`2 sniCerts) in D:\dbs\el\sn2\private\frontend\Serp\app\host\KestrelServerWrapper.cs:line 233

Expected Behavior

Not throw an exception

Steps To Reproduce

We aren't using the web host builder. The new interface or given implementation does not appear to be public.

Exceptions (if any)

No response

.NET Version

8.0.100-preview.5.23303.2

Anything else?

f1bbdd4
@amcasey

@mitchdenny
Copy link
Member

Could you provide a code sample or minimal repro of what you are trying to do?

@timmydo
Copy link
Author

timmydo commented Jun 22, 2023

Here is a minimal repro:
project.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" Version="2.2.1" />
    <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
  </ItemGroup>

</Project>

program.cs:

using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
using Microsoft.Extensions.Options;
using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;

namespace SimpleWebServer
{

    internal class SerpKestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
    {
        private readonly IServiceProvider services;

        public SerpKestrelServerOptionsSetup(IServiceProvider services)
        {
            this.services = services;
        }

        public void Configure(KestrelServerOptions options)
        {
            options.ApplicationServices = this.services;
            options.RequestHeaderEncodingSelector = (_) => System.Text.Encoding.Latin1;
            var cert = new X509Certificate2(Convert.FromBase64String(@"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"));
            options.Listen(IPAddress.Any, 8443, (opt) =>
            {
                opt.UseHttps(cert);
            });
        }
    }

    internal sealed class KestrelApplication : IHttpApplication<HttpContext>
    {
        public KestrelApplication()
        {
        }

        public HttpContext CreateContext(IFeatureCollection features)
        {
            return new DefaultHttpContext(features);
        }

        public void DisposeContext(HttpContext context, Exception? exception)
        {
        }

        public async Task ProcessRequestAsync(HttpContext context)
        {
            context.Response.StatusCode = 429;
            await Task.Delay(1);
        }
    }

    internal class MyServer
    {
        private readonly IServiceProvider services;
        public MyServer()
        {
            this.services = CreateServiceProvider();
        }

        public static IServiceProvider CreateServiceProvider()
        {
            var services = new ServiceCollection();
            services.AddOptions();
            services.AddLogging();

            services.AddSingleton<ILoggerFactory, NoOpLoggerFactory>();
            services.AddSingleton<IServer, KestrelServer>();
            services.AddTransient<IConfigureOptions<KestrelServerOptions>, SerpKestrelServerOptionsSetup>();

            services.AddSingleton<IConnectionListenerFactory, SocketTransportFactory>();

            return services.BuildServiceProvider();
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            var server = this.services.GetRequiredService<IServer>();
            var application = new KestrelApplication();
            await server.StartAsync(application, cancellationToken);
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            var server = this.services.GetRequiredService<IServer>();
            await server.StopAsync(cancellationToken);
        }
    }

    internal sealed class NoOpLogger : ILogger, IDisposable
    {
        public IDisposable? BeginScope<TState>(TState state) where TState : notnull
        {
            return this;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
                return false;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
        }

        public void Dispose()
        {
        }
    }

    internal sealed class NoOpLoggerFactory : ILoggerFactory
    {
        private readonly ILogger logger = new NoOpLogger();

        public void Dispose()
        {
        }

        public void AddProvider(ILoggerProvider provider)
        {
        }

        public ILogger CreateLogger(string categoryName)
        {
            return this.logger;
        }
    }


    internal class Program
    {
        static async Task Main(string[] args)
        {
            var s = new MyServer();
            await s.StartAsync(CancellationToken.None);
            await Task.Delay(99999);
        }
    }
}

@Tratcher
Copy link
Member

The service is only added here, making UseHttps unusable without IWebHostBuilder:

services.AddSingleton<IHttpsConfigurationService, HttpsConfigurationService>();

Though you could provide your own implementation of IWebHostBuilder. Otherwise the service would have to be made public, or at least add a public IServiceCollection extension to unblock you.

@timmydo
Copy link
Author

timmydo commented Jun 22, 2023

@Tratcher actually there's more. I need to also create an IHostEnvironment, KestrelMetrics, and an IMeterFactory:

            var host = new FakeHostEnvironment();

            var assembly = System.Reflection.Assembly.GetAssembly(typeof(KestrelServer))!;
            var typeIHttpsConfigurationService = assembly.GetType("Microsoft.AspNetCore.Server.Kestrel.Core.IHttpsConfigurationService", true, false)!;
            var typeHttpsConfigurationService = assembly.GetType("Microsoft.AspNetCore.Server.Kestrel.Core.HttpsConfigurationService", true, false)!;
            
            services.AddSingleton(typeIHttpsConfigurationService, typeHttpsConfigurationService);
            services.AddSingleton<IHostEnvironment, FakeHostEnvironment>();

            var metrics = assembly.GetType("Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelMetrics", true, false)!;
            services.AddSingleton(metrics);
            services.AddSingleton<IMeterFactory, TestMeterFactory>();


// and

internal class FakeHostEnvironment : IHostEnvironment
{
    public string ApplicationName { get; set; } = "Name";
    public IFileProvider ContentRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
    public string ContentRootPath { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
    public string EnvironmentName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
internal sealed class TestMeterFactory : IMeterFactory
{
    public List<Meter> Meters { get; } = new List<Meter>();

    public Meter Create(MeterOptions options)
    {
        var meter = new Meter(options.Name, options.Version, Array.Empty<KeyValuePair<string, object>>(), scope: this);
        Meters.Add(meter);
        return meter;
    }

    public void Dispose()
    {
        foreach (var meter in Meters)
        {
            meter.Dispose();
        }

        Meters.Clear();
    }
}

it seems the ability to create a server without all the WebHostBuilder plumbing is impossible now. It's disappointing to see all these breaking changes making it difficult for us to migrate from .NET 7 to .NET 8.

@davidfowl
Copy link
Member

@timmydo it would be great if you could express why you're not using one of the documented paths for creating a server instance for testing? Why all of this manual code?

@timmydo
Copy link
Author

timmydo commented Jun 22, 2023

This isn't how I'd create a new server if I got to start from scratch today--this is trying to show a minimal repro from an ASP.NET application that has been around longer than ASP.NET Core...it comes from a time where we had our own abstractions to support multiple different web servers, our own dependency injection library, our own middleware, etc. We want to be able to use parts of ASP.NET Core and Kestrel, so the way it was originally done was by using the core pieces we wanted and not just creating a default web host and all the extras that entailed--because we wanted the control and had a bunch of config/code in other places that needed to be preserved.

Is it a deliberate decision to require web host builder for https scenarios going forward?

@davidfowl
Copy link
Member

davidfowl commented Jun 22, 2023

We have documented ways (too many maybe) of creating a minimal web server, the most recent being:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
await app.RunAsync();

We want to be able to use parts of ASP.NET Core and Kestrel, so the way it was originally done was by using the core pieces we wanted and not just creating a default web host and all the extras that entailed--because we wanted the control and had a bunch of config/code in other places that needed to be preserved.

I'd recommend finding a way to shift to using the hosting layer. There's no real golden path to using kestrel stand alone, it's a quite a bit of manual wire up. While we can and will fix the issues, the entry point to creating a kestrel sever will likely reuse most of these APIs under the covers.

@amcasey
Copy link
Member

amcasey commented Aug 8, 2023

After some offline discussions, I think @timmydo was able to adopt IWebHostBuilder, but it may still be interesting to consider whether we're willing to make a breaking change like this. Given the sneakiness that was required to make the https support trimmable, I'm not sure how reasonable it is to expect people to build that out themselves. If we think more people will run into this, we could probably expose a second extension method on IServiceCollection. I think that would make more sense than exposing the implementation types.

@amcasey
Copy link
Member

amcasey commented Aug 17, 2023

Other things being equal, we would certainly prefer to maintain the invariant that code compiling against 7.0 should continue to compile, unmodified, against 8.0. Unfortunately, in this case, that's at odds with our goal of making HTTPS support opt-in to substantially reduce the size of some trimmed/AOT'd assemblies. As a result, anyone upgrading is going to have to write some new code. Given that, it seems reasonable to encourage anyone in that position to adopt the recommended and supported host builder approach, most conveniently through CreateDefaultBuilder, but alternatively through CreateSlimBuilder or CreateEmptyBuilder if they want maximal control.

Especially given that this is the only request (I know of) for this functionality and that it's been resolved by successfully adopting a host builder, it seems reasonable to close this issue, even though the breaking change is regrettable.

@amcasey amcasey closed this as completed Aug 17, 2023
@amcasey
Copy link
Member

amcasey commented Aug 17, 2023

If you really, really don't want to use a host builder, it's actually possible to create a dummy one that will cause your service collection to be updated appropriately:

internal static class KestrelHelper
{
    public static IServiceCollection AddKestrel(this IServiceCollection serviceCollection, string applicationName, string contentRoot)
    {
        serviceCollection.AddSingleton<IHostEnvironment>(new MyHostEnvironment
        {
            ApplicationName = applicationName,
            ContentRootPath = contentRoot
        });

        serviceCollection.AddSingleton<IMeterFactory, MyMeterFactory>();

        var builder = new MyBuilder(serviceCollection);
        builder.UseKestrel();
        return serviceCollection;
    }

    private sealed class MyBuilder : IWebHostBuilder
    {
        private readonly IServiceCollection _serviceCollection;

        public MyBuilder(IServiceCollection serviceCollection)
        {
            _serviceCollection = serviceCollection;
        }

        IWebHostBuilder IWebHostBuilder.ConfigureServices(Action<IServiceCollection> configureServices)
        {
            configureServices(_serviceCollection);
            return this;
        }


        IWebHost IWebHostBuilder.Build() => throw new NotImplementedException();
        IWebHostBuilder IWebHostBuilder.ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate) => throw new NotImplementedException();
        IWebHostBuilder IWebHostBuilder.ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) => throw new NotImplementedException();
        string IWebHostBuilder.GetSetting(string key) => throw new NotImplementedException();
        IWebHostBuilder IWebHostBuilder.UseSetting(string key, string value) => throw new NotImplementedException();
    }

    private sealed class MyHostEnvironment : IHostEnvironment
    {
        public string EnvironmentName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public string ApplicationName { get; set; }
        public string ContentRootPath { get; set; }
        public IFileProvider ContentRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
    }

    private sealed class MyMeterFactory : IMeterFactory
    {
        public Meter Create(MeterOptions options) => new Meter(options);
        public void Dispose() { }
    }
}

@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Aug 25, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Sep 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

No branches or pull requests

6 participants