WebApplication.CreateBuilder()와 새로운 CreateSlimBuilder() 메서드 비교하기

WebApplication.CreateBuilder()와 새로운 CreateSlimBuilder() 메서드 비교하기

.NET 8 미리 보기 살펴보기 - 파트 3

·

10 min read

Andrew Lock님의 Comparing WebApplication.CreateBuilder() to the new CreateSlimBuilder() method를 DeepL의 도움을 받아 번역하였습니다.

이번 포스팅은 .NET 8 미리 보기 살펴보기 시리즈의 세 번째 포스팅입니다.

1부 - 새로운 구성 바인더 소스 생성기 사용하기
2부 - 미니멀 API AOT 컴파일 템플릿
3부 - WebApplication.CreateBuilder()와 새로운 CreateSlimBuilder() 메서드 비교(이 게시물)
4부 - 새로운 미니멀 API 소스 생성기 살펴보기
5부 - 메서드 호출을 인터셉터로 대체하기

이 게시물에서는 새로운 CreateSlimBuilder 메서드를 살펴봅니다. 이 메서드는 AOT 시나리오를 지원하기 위해 기존 WebApplication.CreateBuilder 메서드의 대안으로 .NET 8에 도입되었습니다. 이 글에서는 슬림 빌더에서 빠진 기능에 대해 개략적으로 설명한 다음 코드를 자세히 살펴보고 어떻게 구현되는지 살펴봅니다.

이 게시물은 모두 프리뷰 빌드를 사용하고 있으므로 2023년 11월에 .NET 8이 최종 출시되기 전에 일부 기능이 변경(또는 제거)될 수 있습니다!

CreateSlimBuilder가 필요한 이유는 무엇인가요?

이전 게시물에서 .NET 8에 도입된 Ahead-of-time(AOT) 미니멀 API api 템플릿을 보여드렸습니다. 해당 템플릿의 첫 번째 줄은

var builder = WebApplication.CreateSlimBuilder(args);

web "빈" 템플릿의 해당 줄(.NET 6-8에서 본질적으로 변경되지 않음)과 비교합니다.

var builder = WebApplication.CreateBuilder(args);

그렇다면 왜 이렇게 바뀐 걸까요?

이전 글에서 설명했듯이 AOT 컴파일의 중요한 부분은 프레임워크와 앱에서 사용되지 않는 부분을 모두 제거하는 트리밍입니다. 이렇게 하면 최종 바이너리 크기가 크게 줄어들며 합리적인 바이너리 크기를 달성하는 데 필요합니다.

"일반적인" JIT 컴파일된 .NET 앱에서는 사용하지 않는 기능이나 필요하지 않은 기능을 포함하기 위해 바이너리 크기가 약간 커지지만, 그 정도는 상대적으로 미미합니다. 사용하지 않는 메서드를 한 번도 호출하지 않으면 컴파일되지 않으며, 많은 어셈블리가 로드되지 않을 수도 있습니다.

AOT를 사용하면 모든 새로운 기능에 대해 비용을 지불해야 합니다. 필요하지 않은 기능을 제거하면 최종 AOT 바이너리 크기에 큰 영향을 미칠 수 있습니다.

이전 글에서 설명했듯이 컴파일러는 앱에서 실제로 사용되는 유형과 메서드를 정적으로 결정할 수 있어야 하는데, 이는 일반적으로 리플렉션 기반 API가 문제가 된다는 의미입니다.

CreateSlimBuilder는 AOT와 호환되지 않거나 서버리스 및 클라우드 네이티브 앱과 같이 AOT가 빛을 발하는 앱에 유용하지 않은 여러 기능을 제거합니다. 이러한 종류의 앱을 대상으로 하지 않더라도 제거된 기능이 필요하지 않은 경우 CreateSlimBuilder를 고려할 수 있습니다. 다음 섹션에서는 이러한 변경 사항이 무엇인지 살펴보겠습니다.

CreateSlimBuilder에서 누락된 기능은 무엇인가요?

CreateSlimBuilder 메서드는 CreateBuilder와 유사합니다. 두 메서드 모두 WebApplicationBuilder를 초기화하지만 CreateSlimBuilder문서에 설명된 대로 앱을 실행하는 데 필요한 최소한의 ASP.NET Core 기능만 초기화합니다. 즉, 누락되거나 변경된 사항이 많이 있습니다.

앱에서 IHostingStartup을 직접 사용해 본 적은 없지만 Azure 앱 서비스(AAS)에 배포해 본 적이 있다면 아마 모르고 사용했을 것입니다! IHostingStartup을 사용하면 런타임에 어셈블리를 로드하여 예를 들어 DI 컨테이너의 서비스를 사용자 지정하여 애플리케이션 구성 방식을 변경할 수 있습니다. 제가 마지막으로 확인한 바로는 AAS가 일부 통합을 추가하는 방식입니다. 하지만 앞서 언급했듯이 AOT에서는 JIT가 없기 때문에 런타임에 임의의 dll을 로드할 수 없습니다!

리플렉션을 사용하여 앱에서 메서드를 호출하기 때문에 AOT에서는 까다로운 또 다른 디자인이므로 UseStartup<>에 대한 지원은 제거되었습니다. 이것은 실제로 AOT 시나리오에서 가능할 수도 있지만, WebApplicationBuilder와 함께 Startup 클래스를 사용하는 것은 사람들이 일반적으로 하는 일이 아니므로 대부분 부피만 늘릴 뿐입니다.

애플리케이션에서 builder.WebHost.UseStartup(); 을 호출하려고 하면 분석기에서 컴파일 타임 오류가 발생하여 런타임에 실패할 것이라는 경고가 표시됩니다. 저는 이와 같은 런타임 문제를 포착하기 위해 분석기를 도입한 ASP.NET 팀의 열렬한 팬입니다!

제거된 나머지 기능의 대부분은 클라우드/서버리스, Linux, 환경 등 AOT가 목표로 하는 시나리오에서 일반적으로 사용되지 않기 때문에 제거되었습니다.

  • 이러한 환경은 Linux인 경우가 많으므로(시작 시간이 걱정된다면 Windows의 IIS 뒤에서 호스팅하지 않을 것입니다) EventLog 및 IIS 지원을 제거하는 것이 합리적입니다.

  • 앱에서 인증서를 관리할 필요가 없도록 TLS 종료 프록시 뒤에서 HTTPS 또는 HTTP/3을 처리하는 것이 일반적입니다.

  • 프로덕션 환경에서는 (다행히도!) 디버거가 첨부되어 있지 않을 것입니다.

  • 앱을 게시할 때 UseStaticWebAssets()는 필요하지 않으며, 일반적으로 API 애플리케이션에서는 사용하지 않습니다.

Regex 경로 제약 조건이 지원되지 않는 것은 순전히 Regex 지원으로 인해 많은 코드가 추가되기 때문이며, 특히 .NET 7 비백트래킹 지원의 경우 더욱 그렇습니다! 기본적으로 인라인 Regex 제약 조건에 대한 지원을 제거하면 바이너리 크기에서 약 1MB가 제거됩니다. 필요한 경우 다음을 사용하여 인라인 정규식 지원을 다시 활성화할 수 있습니다.

var builder = WebApplication.CreateSlimBuilder();

builder.Services.AddRoutingCore().Configure<RouteOptions>(options => {
    options.SetParameterPolicy<RegexInlineRouteConstraint>("regex");
});

로깅 공급자 또는 HTTPS 지원과 같은 일부 기능을 다시 추가하려면 그렇게 할 수 있습니다. 예를 들어 HTTPS 지원을 활성화하려면 builder.WebHost.UseKestrelHttpsConfiguration()을 호출하세요.

CreateSlimBuilder()를 사용하는 데 관심이 있다면 이 정도만 알아두면 됩니다. 다음 섹션에서는 빌더의 모든 실제 변경 사항을 자세히 살펴보겠습니다.

구현 방법

이 섹션에서는 CreateSlimBuilder()가 어떻게 구현되는지, 주로 기존의 CreateBuilder() 메서드와 비교하여 살펴봅니다.

경고: 이 섹션은 초보자를 위한 것이 아니며 매우 건조합니다. 여러분이 알아야 할 것보다 훨씬 더 깊이 있는 내용이며, 대부분 이 글의 전반부를 이해하기 위해 제가 겪은 일들입니다!

WebApplicationBuilder는 .NET 6에 추가되었지만 이전 버전의 .NET Core에 도입된 기존의 모든 일반 IHostBuilderIWebHostBuilder 추상화를 기반으로 합니다. 결과적으로 프랑켄슈타인의 괴물 같은 컴포넌트입니다! 대부분의 경우 CreateSlimBuilder()CreateBuilder()와 매우 유사한 웹 응용 프로그램 빌더를 생성합니다.

이전 포스팅에서 다양한 HostBuilder 인스턴스를 결합하여 WebApplicationBuilder가 어떻게 작동하는지 자세히 살펴봤습니다. WebApplicationBuilder를 전반적으로 더 잘 이해하고 싶으시다면 해당 포스팅을 읽어보시기 바랍니다!

두 메서드가 모두 static 헬퍼로 정의되어 있는 WebApplication 메서드 정의부터 살펴보겠습니다.

public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
    public static WebApplicationBuilder CreateBuilder() =>
        new WebApplicationBuilder(new WebApplicationOptions());

    public static WebApplicationBuilder CreateSlimBuilder() =>
        new WebApplicationBuilder(new WebApplicationOptions(), slim: true);
                                                            // 👆 the only difference
}

보시다시피, 이 메서드들은 서로 다른 WebApplicationBuilder 생성자를 호출하며, 슬림 빌더는 slim: true 값을 전달합니다.

이 두 생성자를 비교해보면(아래 차이점 참조), (다소 놀랍게도) "슬림" 버전에 훨씬 더 많은 추가 코드가 포함되어 있음을 알 수 있습니다! 그 이유는 곧 알게 되겠지만 기본 빌더는 이 작업의 많은 부분을 다른 헬퍼 메서드에 위임하는 반면, 슬림 빌더는 여기서 대부분의 작업을 인라인으로 수행하기 때문입니다(그리고 필요하지 않은 것은 제거합니다). 실제로 "주요" 변경 사항은 두 가지뿐입니다:

슬림 빌더는 새로운 HostApplicationBuilder() 대신 Host.CreateEmptyApplicationBuilder()를 호출합니다. 슬림 빌더는 ConfigureWebHostDefaults() 대신 ConfigureSlimWebHost()ConfigureWebDefaultsCore()를 호출합니다.

- internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null)
+ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<IHostBuilder>? configureDefaults = null)
{
    var configuration = new ConfigurationManager();

    configuration.AddEnvironmentVariables(prefix: "ASPNETCORE_");

+   // SetDefaultContentRoot needs to be added between 'ASPNETCORE_' and 'DOTNET_' in order to match behavior of the non-slim WebApplicationBuilder.
+   SetDefaultContentRoot(options, configuration);

+   // Add the default host environment variable configuration source.
+   // This won't be added by CreateEmptyApplicationBuilder.
+   configuration.AddEnvironmentVariables(prefix: "DOTNET_");

+   _hostApplicationBuilder = Microsoft.Extensions.Hosting.Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings
-   _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings
    {
        Args = options.Args,
        ApplicationName = options.ApplicationName,
        EnvironmentName = options.EnvironmentName,
        ContentRootPath = options.ContentRootPath,
        Configuration = configuration,
    });

+   // Ensure the same behavior of the non-slim WebApplicationBuilder by adding the default "app" Configuration sources
+   ApplyDefaultAppConfigurationSlim(_hostApplicationBuilder.Environment, configuration, options.Args);
+   AddDefaultServicesSlim(configuration, _hostApplicationBuilder.Services);

+   // configure the ServiceProviderOptions here since CreateEmptyApplicationBuilder won't.
+   var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder);
+   _hostApplicationBuilder.ConfigureContainer(serviceProviderFactory);

    // Set WebRootPath if necessary
    if (options.WebRootPath is not null)
    {
        Configuration.AddInMemoryCollection(new[]
        {
            new KeyValuePair<string, string?>(WebHostDefaults.WebRootKey, options.WebRootPath),
        });
    }

    // Run methods to configure web host defaults early to populate services
    var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder);

    // This is for testing purposes
    configureDefaults?.Invoke(bootstrapHostBuilder);

-   bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder =>
+   bootstrapHostBuilder.ConfigureSlimWebHost(webHostBuilder =>
    {
+       AspNetCore.WebHost.ConfigureWebDefaultsCore(webHostBuilder);

        // Runs inline.
        webHostBuilder.Configure(ConfigureApplication);

        webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, _hostApplicationBuilder.Environment.ApplicationName ?? "");
        webHostBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, Configuration[WebHostDefaults.PreventHostingStartupKey]);
        webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, Configuration[WebHostDefaults.HostingStartupAssembliesKey]);
        webHostBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, Configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]);
    },
    options =>
    {
        // We've already applied "ASPNETCORE_" environment variables to hosting config
        options.SuppressEnvironmentConfiguration = true;
    });

    // This applies the config from ConfigureWebHostDefaults
    // Grab the GenericWebHostService ServiceDescriptor so we can append it after any user-added IHostedServices during Build();
    _genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks();

    // Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then
    // grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection.
    var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)];
    Environment = webHostContext.HostingEnvironment;

    Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
    WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
}

CreateEmptyApplicationBuilder()는 간단한 공용 헬퍼 메서드로 새 HostApplicationBuilder()를 생성합니다.

public static class Host
{
    public static HostApplicationBuilder CreateEmptyApplicationBuilder(HostApplicationBuilderSettings? settings)
            => new HostApplicationBuilder(settings, empty: true);
}

따라서 빈/슬림 버전은 이번에는 다른 생성자, 즉 HostApplicationBuilder를 호출합니다. 기본 생성자와 "빈" 생성자(아래)를 비교하면 많은 코드가 제거되었음을 알 수 있습니다. 이 코드의 대부분은 실제로 WebApplicationBuilder 생성자로 옮겨졌지만 몇 가지 주목할 만한 변경 사항이 있습니다.

- public HostApplicationBuilder(HostApplicationBuilderSettings? settings)
+ internal HostApplicationBuilder(HostApplicationBuilderSettings? settings, bool empty)
{
    settings ??= new HostApplicationBuilderSettings();
    Configuration = settings.Configuration ?? new ConfigurationManager();

-    if (!settings.DisableDefaults)
-    {
-        if (settings.ContentRootPath is null && Configuration[HostDefaults.ContentRootKey] is null)
-        {
-            HostingHostBuilderExtensions.SetDefaultContentRoot(Configuration);
-        }
-
-        Configuration.AddEnvironmentVariables(prefix: "DOTNET_");
-    }

    Initialize(settings, out _hostBuilderContext, out _environment, out _logging);

-    ServiceProviderOptions? serviceProviderOptions = null;
-    if (!settings.DisableDefaults)
-    {
-        HostingHostBuilderExtensions.ApplyDefaultAppConfiguration(_hostBuilderContext, Configuration, settings.Args);
-        HostingHostBuilderExtensions.AddDefaultServices(_hostBuilderContext, Services);
-        serviceProviderOptions = HostingHostBuilderExtensions.CreateDefaultServiceProviderOptions(_hostBuilderContext);
-    }

    _createServiceProvider = () =>
    {
        // Call _configureContainer in case anyone adds callbacks via HostBuilderAdapter.ConfigureContainer<IServiceCollection>() during build.
        // Otherwise, this no-ops.
        _configureContainer(Services);
-       return serviceProviderOptions is null ? Services.BuildServiceProvider() : Services.BuildServiceProvider(serviceProviderOptions);
+       return Services.BuildServiceProvider();
    };
}

다음으로 ConfigureWebHostConfigureSlimWebHost의 차이점을 살펴보겠습니다. 아래에서 볼 수 있듯이, 이 확장 메서드들은 각각 다른 IWebHostBuilder 구현을 생성합니다. GenericWebHostBuilderSlimWebHostBuilder를 각각 생성합니다.

- public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder)
+ public static IHostBuilder ConfigureSlimWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder)
{
    return ConfigureWebHost(
        builder,
-       static (hostBuilder, options) => new GenericWebHostBuilder(hostBuilder, options),
+       static (hostBuilder, options) => new SlimWebHostBuilder(hostBuilder, options),
        configure,
        configureWebHostBuilder);
}

이 두 클래스의 생성자를 비교해보면, SlimWebHostBuilder는 실제로는 GenericWebHostBuilder 의 슬림 버전입니다. 앞서 설명한 이유 때문에 두 가지 기능이 생략되었습니다.

  • 호스팅 어셈블리(IHostingStartup) 지원이 제거됨

  • UseStartup<T> 지원이 제거됨

+ public SlimWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
- public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
    : base(builder, options)
{
    _builder.ConfigureHostConfiguration(config =>
    {
        config.AddConfiguration(_config);

-       // We do this super early but still late enough that we can process the configuration
-       // wired up by calls to UseSetting
-       ExecuteHostingStartups();
    });

-   // IHostingStartup needs to be executed before any direct methods on the builder
-   // so register these callbacks first
-   _builder.ConfigureAppConfiguration((context, configurationBuilder) =>
-   {
-       if (_hostingStartupWebHostBuilder != null)
-       {
-           var webhostContext = GetWebHostBuilderContext(context);
-           _hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
-       }
-   });

    _builder.ConfigureServices((context, services) =>
    {
        var webhostContext = GetWebHostBuilderContext(context);
        var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];

        // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting
        services.AddSingleton(webhostContext.HostingEnvironment);
#pragma warning disable CS0618 // Type or member is obsolete
        services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
        services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
#pragma warning restore CS0618 // Type or member is obsolete

        services.Configure<GenericWebHostServiceOptions>(options =>
        {
            // Set the options
            options.WebHostOptions = webHostOptions;
-           // Store and forward any startup errors
-           options.HostingStartupExceptions = _hostingStartupErrors;
        });

        // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up
        // We need to flow this differently
        services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
        services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
        services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
        services.TryAddSingleton(DistributedContextPropagator.Current);

        services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
        services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
        services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();

        services.AddMetrics();
        services.TryAddSingleton<HostingMetrics>();

-       // IMPORTANT: This needs to run *before* direct calls on the builder (like UseStartup)
-       _hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);

-       // Support UseStartup(assemblyName)
-       if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
-       {
-           ScanAssemblyAndRegisterStartup(context, services, webhostContext, webHostOptions);
-       }
    });
}

다음으로 ConfigureWebDefaultsConfigureWebDefaultsCore의 차이점에 대해 알아보겠습니다. 전자기본 ConfigureWebHostDefaults() 메서드에서 호출됩니다("일반" WebApplicationBuilder 생성자에서 호출됨). 후자는 "슬림" WebApplicationBuilder 생성자 내부에서 직접 호출됩니다.

여기서 분명한 차이점은 슬림 버전은 앞서 설명한 것처럼 IIS 통합이나 정적 웹 자산 어셈블리를 추가하지 않는다는 것입니다. 하지만 Kestrel을 구성하는 ConfigureWebDefaultsWorker() 메서드가 호출되는 방식에도 차이가 있습니다.

- internal static void ConfigureWebDefaults(IWebHostBuilder builder)
+ internal static void ConfigureWebDefaultsCore(IWebHostBuilder builder)
{
-   builder.ConfigureAppConfiguration((ctx, cb) =>
-   {
-       if (ctx.HostingEnvironment.IsDevelopment())
-       {
-           StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
-       }
-   });

    ConfigureWebDefaultsWorker(
-        builder.UseKestrel(ConfigureKestrel),
+        builder.UseKestrelCore().ConfigureKestrel(ConfigureKestrel), 
-        configureRouting: services => services.AddRouting()
+        configureRouting: null
    );

-   builder
-       .UseIIS()
-       .UseIISIntegration();
}

이 구성의 차이를 비교하기 위해 "원래" UseKestrel(ConfigureKestrel) 메서드를 인라인으로 사용하면 다음과 같은 결과를 얻을 수 있습니다.

- builder.UseKestrel().ConfigureKestrel(ConfigureKestrel)
+ builder.UseKestrelCore().ConfigureKestrel(ConfigureKestrel),

이제 유일한 차이점은 UseKestrel()UseKestrelCore()라는 것이 더 분명해졌습니다. UseKestrel()을 살펴보면 기본 빌더와 슬림 빌더의 Kestrel 구성에서 유일한 차이점은 예상대로 HTTPS와 Quic 지원뿐이라는 것을 알 수 있습니다.

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
    return hostBuilder
        .UseKestrelCore()
        .UseKestrelHttpsConfiguration() // 👈 missing in the "slim" builder
        .UseQuic(options => // 👈 missing in the "slim" builder
        {
            // Configure server defaults to match client defaults.
            // https://github.com/dotnet/runtime/blob/a5f3676cc71e176084f0f7f1f6beeecd86fbeafc/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs#L118-L119
            options.DefaultStreamErrorCode = (long)Http3ErrorCode.RequestCancelled;
            options.DefaultCloseErrorCode = (long)Http3ErrorCode.NoError;
        });
}

마지막으로 빌더 간에 ConfigureWebDefaultsWorker가 호출되는 방식에 한 가지 차이점이 있습니다:

  • 기본 버전에서는 ConfigureWebDefaultsWorker()AddRouting()을 호출하는 람다가 전달됩니다.

  • 슬림 버전에서는 null이 전달되므로 ConfigureWebDefaultsWorker()는 대신 AddRoutingCore()를 호출합니다.

AddRouting()의 코드를 살펴보면 여기에서 기본 빌더에 정규식 경로 제약 조건이 추가되는 것을 볼 수 있습니다(또는 보는 방식에 따라 슬림 빌더에서 제거되는 것을 볼 수 있습니다!)

public static IServiceCollection AddRouting(this IServiceCollection services)
{
    services.AddRoutingCore();
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<RouteOptions>, RegexInlineRouteConstraintSetup>());
    return services;
}

여기까지입니다! 빌더 간의 차이점을 파악하기 위해 GitHub의 모든 코드와 Diff를 탐색하는 데 오랜 시간이 걸렸기 때문에 이 글의 후반부에서는 대부분 제가 찾은 내용을 문서화하는 데만 집중했습니다. 이 글의 전반부(또는 문서)에서 설명한 큰 수준의 차이점만 이해했다면 이 글의 후반부도 충분히 이해할 수 있습니다!

요약

이번 포스팅에서는 .NET 8 프리뷰에서 AOT 호환 api 템플릿을 지원하기 위해 도입된 WebApplication.CreateSlimBuilder() 메서드에 대해 살펴보았습니다. 새로운 메서드가 필요한 이유와 이 메서드를 사용할 때 기존 WebApplication.CreateBuilder() 메서드와 비교했을 때 어떤 차이가 있는지 살펴봤습니다. 글의 후반부에서는 이러한 변경이 내부적으로 어떻게 이루어졌는지 이해하기 위해 GitHub의 모든 실제 코드 차이점을 자세히 살펴봤습니다.