Skip to content

"Minimal hosting" for ASP.NET Core applications #30354

Closed
@davidfowl

Description

@davidfowl

Summary

We want to introduce a new "direct hosting" model for ASP.NET Core applications. This is a more focused, low ceremony way of creating a web application.

Motivation and goals

Introducing a lower ceremony replacement for the WebHost to remove some of the ceremony in hosting ASP.NET Core applications. We've received lots of feedback over the years about how much ceremony it is to get a simple API up and running and we have a chance to improve that with the deprecation of the WebHost.

In scope

  • Build on the same primitives as ASP.NET Core
  • Take advantage of existing ASP.NET Core middleware and frameworks built on top
  • Ability to use existing extension methods on the IServiceCollection, IHostBuilder and IWebHostBuilder

Out of scope

  • Changing the DI registration model
  • Testability - While this is possible makes it very hard to reduce some of the ceremony

Risks / unknowns

  • Having multiple ways to build a web application.
  • Tools are broken
    • EF Core Tools (for example, migration) try to invoke Program.CreateHostBuilder() which no longer exists
    • Unit testing with Test Server

Strawman proposal

The idea is to reduce the number of concepts while keeping compatibility with the ecosystem we have today. Some core ideas in this new model is to:

  • Reduce the number of callbacks used to configure top level things
  • Expose the number of top level properties for things people commonly resolve in Startup.Configure. This allows them to avoid using the service locator pattern for IConfiguration, ILogger, IHostApplicationLifetime.
  • Merge the IApplicationBuilder, the IEndpointRouteBuilder and the IHost into a single object. This makes it easy to register middleware and routes without needed an additional level of lambda nesting (see the first point).
  • Merge the IConfigurationBuilder, IConfiguration, and IConfigurationRoot into a single Configuration type so that we can access configuration while it's being built. This is important since you often need configuration data as part of configuring services.
  • UseRouting and UseEndpoints are called automatically (if they haven't already been called) at the beginning and end of the pipeline.
public class WebApplicationBuilder
{
    public IWebHostEnvironment Environment { get; }
    public IServiceCollection Services { get; }
    public Configuration Configuration { get; }
    public ILoggingBuilder Logging { get; }

    // Ability to configure existing web host and host
    public ConfigureWebHostBuilder WebHost { get; }
    public ConfigureHostBuilder Host { get; }

    public WebApplication Build();
}

public class Configuration : IConfigurationRoot, IConfiguration, IConfigurationBuilder { }

// The .Build() methods are explicitly implemented interface method that throw NotSupportedExceptions
public class ConfigureHostBuilder : IHostBuilder { }
public class ConfigureWebHostBuilder : IWebHostBuilder { }

public class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
    // Top level properties to access common services
    public ILogger Logger { get; }
    public IEnumerable<string> Addresses { get; }
    public IHostApplicationLifetime Lifetime { get; }
    public IServiceProvider Services { get; }
    public IConfiguration Configuration { get; }
    public IWebHostEnvironment Environment { get; }

    // Factory methods
    public static WebApplication Create(string[] args);
    public static WebApplication Create();
    public static WebApplicationBuilder CreateBuilder();
    public static WebApplicationBuilder CreateBuilder(string[] args);

    // Methods used to start the host
    public void Run(params string[] urls);
    public void Run();
    public Task RunAsync(params string[] urls);
    public Task RunAsync(CancellationToken cancellationToken = default);
    public Task StartAsync(CancellationToken cancellationToken = default);
    public Task StopAsync(CancellationToken cancellationToken = default);

    public void Dispose();
    public ValueTask DisposeAsync();
}

Examples

Hello World

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

var app = WebApplication.Create(args);

app.MapGet("/", async http =>
{
    await http.Response.WriteAsync("Hello World");
});

await app.RunAsync();

Hello MVC

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

await app.RunAsync();

public class HomeController
{
    [HttpGet("/")]
    public string HelloWorld() => "Hello World";
}

Integrated with 3rd party ASP.NET Core based frameworks (Carter)

using Carter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCarter();

var app = builder.Build();

app.Listen("http://localhost:3000");

app.MapCarter();

await app.RunAsync();

public class HomeModule : CarterModule
{
    public HomeModule()
    {
        Get("/", async (req, res) => await res.WriteAsync("Hello from Carter!"));
    }
}

More complex, taking advantage of the existing ecosystem of extension methods

using System.Threading.Tasks;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddYamlFile("appsettings.yml", optional: true);

builder.Host.UseSerilog((context, configuration)
    => configuration
        .Enrich
        .FromLogContext()
        .WriteTo
        .Console()
    );

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

builder.Host.ConfigureContainer<ContainerBuilder>(b =>
{
    // Register services using Autofac specific methods here
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.MapGet("/", async http =>
{
    await http.Response.WriteAsync("Hello World");
});

await app.RunAsync("http://localhost:3000");

cc @LadyNaggaga @halter73 @shirhatti

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcdesign-proposalThis issue represents a design proposal for a different issue, linked in the descriptionenhancementThis issue represents an ask for new feature or an enhancement to an existing onefeature-minimal-hosting

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions