Note
이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 10 버전을 참조하세요.
이 문서:
- 최소 API에 대한 빠른 참조를 제공합니다.
- 숙련된 개발자를 대상으로 합니다. 소개는 자습서: ASP.NET Core를 사용하여 최소 API 만들기를 참조 하세요.
최소 API는 다음으로 구성됩니다.
WebApplication
다음 코드는 ASP.NET Core 템플릿에서 생성됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
위의 코드는 명령줄에서 dotnet new web을 통해 만들거나 Visual Studio에서 빈 웹 템플릿을 선택하여 만들 수 있습니다.
다음 코드는 WebApplication를 명시적으로 만들지 않고 app(WebApplicationBuilder)을 만듭니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create는 미리 구성된 기본값을 사용하여 WebApplication 클래스의 새 인스턴스를 초기화합니다.
WebApplication 는 특정 조건에 따라 최소 API 애플리케이션 에 다음 미들웨어를 자동으로 추가합니다.
-
UseDeveloperExceptionPage가HostingEnvironment일 때"Development"가 먼저 추가됩니다. - 사용자 코드가 아직
UseRouting를 호출하지 않은 경우 및 구성된 엔드포인트가 있는 경우(예:UseRouting),app.MapGet가 두 번째로 추가됩니다. -
UseEndpoints는 엔드포인트가 구성된 경우 미들웨어 파이프라인의 끝에 추가됩니다. -
UseAuthentication은 사용자 코드가 아직 호출UseRouting되지 않은 경우와 서비스 공급자에서 검색할 수 있는 경우UseAuthentication즉시IAuthenticationSchemeProvider추가됩니다.IAuthenticationSchemeProvider은AddAuthentication를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. -
UseAuthorization는 사용자 코드가 아직 호출UseAuthorization되지 않았고 서비스 공급자에서 검색할 수 있는 경우IAuthorizationHandlerProvider다음에 추가됩니다.IAuthorizationHandlerProvider은AddAuthorization를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. - 사용자 구성 미들웨어 및 엔드포인트는
UseRouting및UseEndpoints사이에 추가됩니다.
다음 코드는 앱에 추가되는 자동 미들웨어가 생성하는 것입니다.
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
경우에 따라 앱에 대한 기본 미들웨어 구성이 올바르지 않으며 수정이 필요합니다. 예를 들어, UseCors은 UseAuthentication 및 UseAuthorization 전에 호출되어야 합니다. 앱은 호출 UseAuthentication 해야 하며 UseAuthorization 호출되는 경우 UseCors :
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
경로 일치가 발생하기 전에 미들웨어를 실행해야 하는 경우, UseRouting를 호출해야 하며 UseRouting에 대한 호출 전에 미들웨어를 배치해야 합니다.
UseEndpoints 는 앞에서 설명한 대로 자동으로 추가되므로 이 경우에는 필요하지 않습니다.
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
터미널 미들웨어를 추가하는 경우:
- 미들웨어는
UseEndpoints다음에 추가해야 합니다. - 터미널 미들웨어를 올바른 위치에 배치할 수 있도록 앱은
UseRouting및UseEndpoints를 호출해야 합니다.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
터미널 미들웨어는 요청을 처리하는 엔드포인트가 없는 경우 실행되는 미들웨어입니다.
포트 작업
Visual Studio 또는 dotnet new를 사용하여 웹앱을 만들면 앱이 응답하는 포트를 지정하는 Properties/launchSettings.json 파일이 만들어집니다. 다음에 오는 포트 설정 샘플에서는 Visual Studio에서 앱을 실행하면 Unable to connect to web server 'AppName' 오류 대화 상자가 반환됩니다. Visual Studio는 Properties/launchSettings.json에 지정된 포트가 예상되기 때문에 오류를 반환하지만 앱은 app.Run("http://localhost:3000")에서 지정한 포트를 사용하고 있습니다. 명령줄에서 다음 포트 변경 샘플을 실행합니다.
다음 섹션에서는 앱이 응답하는 포트를 설정합니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
위의 코드에서 앱은 포트 3000에 응답합니다.
여러 포트
위의 코드에서 앱은 포트 3000 및 4000에 응답합니다.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
명령줄에서 포트 설정
다음 명령은 앱이 포트 7777에 응답하도록 합니다.
dotnet run --urls="https://localhost:7777"
Kestrel 엔드포인트가 appsettings.json 파일에도 구성된 경우 appsettings.json 파일에서 지정된 URL이 사용됩니다. 자세한 내용은 Kestrel 엔드포인트 구성을 참조하세요.
환경에서 포트 읽기
다음 코드는 환경에서 포트를 읽습니다.
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
환경에서 포트를 설정하는 기본 방법은 다음 섹션에 나오는 ASPNETCORE_URLS 환경 변수를 사용하는 것입니다.
ASPNETCORE_URLS 환경 변수를 통해 포트 설정
ASPNETCORE_URLS 환경 변수를 사용하여 포트를 설정할 수 있습니다.
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS는 여러 URL을 지원합니다.
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
모든 인터페이스에서 수신 대기
다음 샘플은 모든 인터페이스에서의 수신 대기를 보여 줍니다.
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
ASPNETCORE_URLS를 사용하여 모든 인터페이스에서 수신 대기
이전 샘플은 ASPNETCORE_URLS를 사용할 수 있습니다.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
ASPNETCORE_HTTPS_PORTS 사용하여 모든 인터페이스에서 수신 대기
위의 샘플은 사용할 ASPNETCORE_HTTPS_PORTS 수 있습니다.ASPNETCORE_HTTP_PORTS
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
자세한 내용은 ASP.NET Core Kestrel 웹 서버에 대한 엔드포인트 구성을 참조하세요.
개발 인증서로 HTTPS 지정
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
개발 인증서에 대한 자세한 내용은 Windows 및 macOS에서 ASP.NET Core HTTPS 개발 인증서 신뢰를 참조하세요.
사용자 지정 인증서를 사용하여 HTTPS 지정
다음 섹션에서는 파일 및 구성을 통해 사용자 지정 인증서를 지정하는 appsettings.json 방법을 보여 있습니다.
appsettings.json을 사용하여 사용자 지정 인증서 지정
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
구성을 통해 사용자 지정 인증서 지정
var builder = WebApplication.CreateBuilder(args);
// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
인증서 API 사용
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");
httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
환경 읽기
var app = WebApplication.Create(args);
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}
app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");
app.Run();
환경을 사용하는 방법에 대한 자세한 내용은 ASP.NET Core 런타임 환경을 참조하세요.
Configuration
다음 코드는 구성 시스템에서 읽습니다.
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
자세한 내용은 ASP.NET Core의 구성을 참조하세요.
Logging
다음 코드는 로그온 애플리케이션 시작에 메시지를 씁니다.
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
자세한 내용은 .NET 및 ASP.NET Core의 로깅을 참조하세요.
DI(종속성 주입) 컨테이너 액세스
다음 코드에서는 애플리케이션을 시작하는 동안 DI 컨테이너에서 서비스를 가져오는 방법을 보여줍니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();
var app = builder.Build();
app.MapControllers();
using (var scope = app.Services.CreateScope())
{
var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}
app.Run();
다음 코드에서는 특성을 사용하여 DI 컨테이너에서 키에 액세스하는 [FromKeyedServices] 방법을 보여 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
DI에 대한 자세한 내용은 ASP.NET Core의 종속성 주입을 참조 하세요.
WebApplicationBuilder
이 섹션에는 WebApplicationBuilder를 사용하는 샘플 코드가 포함되어 있습니다.
콘텐츠 루트, 애플리케이션 이름, 환경 변경
다음 코드는 콘텐츠 루트, 애플리케이션 이름, 환경을 설정합니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다.
자세한 내용은 ASP.NET Core 기본 사항 개요를 참조하세요
환경 변수 또는 명령줄을 사용하여 콘텐츠 루트, 앱 이름 및 환경 변경
다음 표는 콘텐츠 루트, 앱 이름, 환경 변경에 사용되는 환경 변수와 명령줄 인수를 보여 줍니다.
| feature | 환경 변수 | 명령줄 인수 |
|---|---|---|
| 애플리케이션 이름 | ASPNETCORE_APPLICATIONNAME | --applicationName |
| 환경 이름 | ASPNETCORE_ENVIRONMENT | --environment |
| 콘텐츠 루트 | ASPNETCORE_CONTENTROOT | --contentRoot |
구성 공급자 추가
다음 샘플은 INI 구성 공급자를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
자세한 내용은 ASP.NET Core의 구성에서 파일 구성 공급자를 참조하세요.
구성 읽기
기본적으로 WebApplicationBuilder는 다음을 포함하여 여러 원본에서 구성을 읽습니다.
-
appSettings.json및appSettings.{environment}.json - 환경 변수
- 명령줄
읽은 구성 원본의 전체 목록은 ASP.NET Core의 구성에서 기본 구성을 참조하세요.
다음 코드는 구성에서 HelloKey를 읽고 / 엔드포인트에 값을 표시합니다. 구성 값이 null이면 "Hello"가 message에 할당됩니다.
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
환경 읽기
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
로깅 공급자 추가
var builder = WebApplication.CreateBuilder(args);
// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();
var app = builder.Build();
app.MapGet("/", () => "Hello JSON console!");
app.Run();
서비스 추가
var builder = WebApplication.CreateBuilder(args);
// Add the memory cache services.
builder.Services.AddMemoryCache();
// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();
IHostBuilder 사용자 지정
IHostBuilder의 기존 확장 메서드는 호스트 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
IWebHostBuilder 사용자 지정
IWebHostBuilder의 확장 메서드는 WebApplicationBuilder.WebHost 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
웹 루트 변경
기본적으로 웹 루트는 wwwroot 폴더의 콘텐츠 루트를 기준으로 합니다. 웹 루트는 정적 파일 미들웨어가 정적 파일을 찾는 위치입니다. 웹 루트는 WebHostOptions, 명령줄 또는 UseWebRoot 메서드를 사용하여 변경할 수 있습니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
사용자 지정 DI(종속성 주입) 컨테이너
다음 예제에서는 Autofac을 사용합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
미들웨어 추가
기존 ASP.NET Core 미들웨어는 WebApplication에서 구성할 수 있습니다.
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.
개발자 예외 페이지
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다. 개발자 예외 페이지는 미리 구성된 기본값에서 사용하도록 설정됩니다.
개발 환경에서 다음 코드가 실행되는 경우 /로 이동하면 예외를 보여 주는 친숙한 페이지가 렌더링됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ASP.NET Core 미들웨어 기본 사항
다음 표에서는 최소 API와 함께 자주 사용되는 일부 미들웨어를 나열합니다.
| Middleware | Description | API |
|---|---|---|
| Authentication | 인증 지원을 제공합니다. | UseAuthentication |
| Authorization | 권한 부여 지원을 제공합니다. | UseAuthorization |
| CORS | 원본 간 리소스 공유를 구성합니다. | UseCors |
| 예외 처리기 | 미들웨어 파이프라인이 throw하는 예외를 전역으로 처리합니다. | UseExceptionHandler |
| 전달된 헤더 | 프록시된 헤더를 현재 요청에 전달합니다. | UseForwardedHeaders |
| HTTPS 리디렉션 | 모든 HTTP 요청을 HTTPS로 리디렉션합니다. | UseHttpsRedirection |
| HSTS(HTTP 엄격한 전송 보안) | 특별한 응답 헤더를 추가하는 보안 향상 미들웨어입니다. | UseHsts |
| 요청 로깅 | HTTP 요청 및 응답 로깅을 지원합니다. | UseHttpLogging |
| 요청 시간 제한 | 요청 시간 제한, 전역 기본값 및 엔드포인트당 구성을 지원합니다. | UseRequestTimeouts |
| W3C 요청 로깅 | W3C 형식의 HTTP 요청 및 응답 로깅을 지원합니다. | UseW3CLogging |
| 응답 캐싱 | 응답 캐시에 대한 지원을 제공합니다. | UseResponseCaching |
| 응답 압축 | 응답 압축에 대한 지원을 제공합니다. | UseResponseCompression |
| Session | 사용자 세션 관리에 대한 지원을 제공합니다. | UseSession |
| 정적 파일 | 정적 파일 및 디렉터리 검색 처리에 대한 지원을 제공합니다. | UseStaticFiles, UseFileServer |
| WebSockets | WebSocket 프로토콜을 활성화합니다. | UseWebSockets |
다음 섹션에서는 요청 처리에 대해 설명합니다. 라우팅, 매개 변수 바인딩 및 응답.
Routing
구성된 WebApplication 지원 및 Map{Verb}MapMethods 낙타 대/소문자 HTTP 메서드(예: {Verb}, GetPost또는Put:Delete
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
이러한 메서드에 전달된 Delegate 인수를 "경로 처리기"라고 합니다.
경로 처리기
경로 처리기는 경로가 일치할 때 실행되는 메서드입니다. 경로 처리기는 람다 식, 로컬 함수, 인스턴스 메서드 또는 정적 메서드일 수 있습니다. 경로 처리기는 동기 또는 비동기일 수 있습니다.
람다 식
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
로컬 함수
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
인스턴스 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new HelloHandler();
app.MapGet("/", handler.Hello);
app.Run();
class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}
정적 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
외부에 정의된 엔드포인트 Program.cs
최소 API는 에 Program.cs위치할 필요가 없습니다.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
이 문서의 뒷부분에 있는 경로 그룹도 참조하세요.
명명된 엔드포인트 및 링크 생성
경로의 URL을 생성하기 위해 경로에 이름을 지정할 수 있습니다. 이름이 지정된 경로를 사용하면 앱에서 경로를 하드 코드로 작성할 필요가 없습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
이전 코드는 The link to the hello route is /hello 엔드포인트에서 /를 표시합니다.
참고: 엔드포인트 이름은 대/소문자를 구분합니다.
엔드포인트 이름:
- 전역적으로 고유해야 합니다.
- OpenAPI 지원을 사용할 때 OpenAPI 작업 ID로 사용됩니다. 자세한 내용은 OpenAPI를 참조하세요.
경로 매개 변수
경로 패턴 정의의 일부로 경로 매개 변수를 캡처할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");
app.Run();
이전 코드는 The user id is 3 and book id is 7 URI에서 /users/3/books/7을 반환합니다.
경로 처리기는 캡처할 매개 변수를 선언할 수 있습니다. 캡처하도록 선언된 매개 변수가 있는 경로에 대한 요청이 이루어지면 매개 변수가 구문 분석되어 처리기에 전달됩니다. 이렇게 하면 형식이 안전한 방식으로 값을 쉽게 캡처할 수 있습니다. 이전 코드에서 userId와 bookId는 모두 int입니다.
이전 코드에서 경로 값 중 하나를 int로 변환할 수 없는 경우 예외가 throw됩니다. GET 요청 /users/hello/books/3는 다음 예외를 throw합니다.
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
와일드카드 및 catch all 경로
다음 catch all 경로는 `/posts/hello` 엔드포인트에서 Routing to hello를 반환합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
경로 제약 조건
경로 제약 조건은 경로의 일치 동작을 제한합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");
app.Run();
다음 표는 이전 경로 템플릿과 동작을 보여 줍니다.
| 경로 템플릿 | URI 일치 예제 |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
자세한 내용은 ASP.NET Core의 라우팅에서 경로 제약 조건 참조를 참조하세요.
경로 그룹
MapGroup확장 메서드는 공통 접두사를 사용하여 엔드포인트 그룹을 구성하는 데 도움이 됩니다. 반복 코드를 줄이고 RequireAuthorization를 추가하는 WithMetadata이나 와 같은 메서드로서 단일 호출로 엔드포인트 전체 그룹을 사용자 지정할 수 있게 됩니다.
예를 들어 다음 코드에서는 두 개의 유사한 엔드포인트 그룹을 만듭니다.
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
이 시나리오에서는 Location 결과에서 201 Created 헤더에 대한 상대 주소를 사용할 수 있습니다.
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
첫 번째 엔드포인트 그룹은 접두사로 지정된 요청만 /public/todos 매칭하게 되며 인증 없이 액세스할 수 있습니다. 두 번째 엔드포인트 그룹은 접두사 /private/todos를 가지고 인증이 필요한 요청만 일치합니다.
QueryPrivateTodos
엔드포인트 필터 팩터리는 프라이빗 할 일 데이터에 액세스하고 저장할 수 있도록 경로 처리기의 TodoDb 매개 변수를 수정하는 로컬 함수입니다.
경로 그룹은 경로 매개 변수 및 제약 조건이 있는 중첩 그룹 및 복잡한 접두사 유형도 지원합니다. 다음 예제에서 그룹에 매핑된 user 경로 처리기는 외부 그룹 접두사에 정의된 {org}와 {group} 경로 매개 변수를 캡처할 수 있습니다.
접두사는 비어 있을 수도 있습니다. 이 기능은 경로 패턴을 변경하지 않고 엔드포인트 메타데이터 또는 필터를 엔드포인트 그룹에 추가하는 데 유용할 수 있습니다.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
그룹에 필터 또는 메타데이터를 추가하면 내부 그룹 또는 특정 엔드포인트에 추가될 수 있는 추가 필터 또는 메타데이터를 추가하기 전에 각 엔드포인트에 개별적으로 추가하는 것과 동일한 결과가 됩니다.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
위의 예제에서 외부 필터는 두 번째로 추가된 경우에도 내부 필터 앞에 들어오는 요청을 기록하게 됩니다. 필터가 서로 다른 그룹에 적용되었기 때문에 필터가 추가된 상대적 순서는 중요하지 않습니다. 동일한 그룹 또는 특정 엔드포인트에 적용되는 경우 주문 필터가 추가됩니다.
/outer/inner/에 대한 요청은 다음을 기록합니다.
/outer group filter
/inner group filter
MapGet filter
매개 변수 바인딩
매개 변수 바인딩은 경로 처리기가 표현하는 강력한 형식의 매개 변수로 요청 데이터를 변환하는 프로세스입니다. 바인딩 소스는 매개 변수가 바인딩되는 위치를 결정합니다. 바인딩 소스는 HTTP 메서드 및 매개 변수 형식에 따라 명시적이거나 유추될 수 있습니다.
지원되는 바인딩 소스:
- 경로 값
- 쿼리 문자열
- Header
- 본문(JSON 형식)
- 양식 값
- 종속성 주입에서 제공하는 서비스
- Custom
다음 GET 경로 처리기는 이러한 매개 변수 바인딩 원본 중 일부를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", (int id,
int page,
[FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
Service service) => { });
class Service { }
주요 매개 변수 바인딩 기능
-
명시적 바인딩: , ,
[FromRoute],[FromQuery][FromHeader][FromBody][FromForm]등의 특성을[FromServices]사용하고 바인딩 소스를 명시적으로 지정합니다. -
양식 바인딩:
[FromForm]특성을 사용하여 양식 값을 바인딩하며, 이는IFormFile및IFormFileCollection을 파일 업로드에 지원합니다. - 복합 형식: 양식, 쿼리 문자열 및 헤더의 컬렉션 및 복합 형식에 바인딩합니다.
-
사용자 지정 바인딩:
TryParse,BindAsync또는IBindableFromHttpContext<T>인터페이스를 사용하여 사용자 지정 바인딩 논리를 구현합니다. - 선택적 매개 변수: 선택적 매개 변수에 대해 nullable 형식 및 기본값을 지원합니다.
- 종속성 주입: 매개 변수는 DI 컨테이너에 등록된 서비스에서 자동으로 바인딩됩니다.
-
특수 형식:
HttpContext,HttpRequest,HttpResponse,CancellationToken,ClaimsPrincipal,Stream, 및PipeReader에 대한 자동 바인딩.
더 알아보세요: 고급 시나리오, 유효성 검사, 바인딩 우선 순위 및 문제 해결을 포함한 매개 변수 바인딩에 대한 자세한 내용은 최소 API 애플리케이션의 매개 변수 바인딩을 참조하세요.
최소 API의 Json+PipeReader 역직렬화
.NET 10부터 ASP.NET Core의 다음 기능 영역은 Stream 대신 PipeReader 기반의 JsonSerializer.DeserializeAsync 오버로드를 사용합니다.
- 최소 API(매개 변수 바인딩, 읽기 요청 본문)
- MVC(입력 포맷터, 모델)
- HttpRequestJsonExtensions 요청 본문을 JSON으로 읽는 Extension 메서드입니다.
대부분의 애플리케이션에서 Stream에서 PipeReader로 전환하면 애플리케이션 코드를 변경하지 않고도 성능이 향상됩니다. 그러나 애플리케이션에 사용자 지정 변환기가 있는 경우 변환기가 올바르게 처리 Utf8JsonReader.HasValueSequence 되지 않을 수 있습니다. 그렇지 않으면 역직렬화할 때 데이터 누락과 같은 ArgumentOutOfRangeException 오류가 발생할 수 있습니다. PipeReader 관련 오류 없이 변환기가 작동하도록 하기 위한 다음 옵션이 있습니다.
옵션 1: 임시 해결 방법
빠른 해결 방법은 PipeReader 지원 없이 Stream을 다시 사용하는 것입니다. 이 옵션을 구현하려면 "Microsoft.AspNetCore.UseStreamBasedJsonParsing" AppContext 스위치를 "true"로 설정합니다. 임시 해결 방법으로만 이 작업을 수행하고 가능한 한 빨리 지원 HasValueSequence 하도록 변환기를 업데이트하는 것이 좋습니다. 스위치는 .NET 11에서 제거될 수 있습니다. 유일한 목적은 개발자에게 변환기를 업데이트할 시간을 제공하는 것이었습니다.
옵션 2: JsonConverter 구현을 위한 빠른 수정
이 수정에서는 ReadOnlySequence에서 배열을 할당합니다. 이 예제에서는 코드의 모양을 보여줍니다.
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
// previous code
}
옵션 3: 더 복잡하지만 더 나은 성능 수정
이 수정에는 처리를 위한 별도의 코드 경로 설정이 ReadOnlySequence 포함됩니다.
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.HasValueSequence)
{
reader.ValueSequence;
// ReadOnlySequence optimized path
}
else
{
reader.ValueSpan;
// ReadOnlySpan optimized path
}
}
자세한 내용은
최소 API의 유효성 검사 지원
유효성 검사를 사용하도록 설정하면 ASP.NET Core 런타임이 다음에서 정의된 유효성 검사를 수행할 수 있습니다.
- Query
- Header
- 요청 메시지 본문
유효성 검사는 네임스페이스의 특성을 DataAnnotations 사용하여 정의됩니다.
최소 API 엔드포인트에 대한 매개 변수가 클래스 또는 레코드 형식인 경우 유효성 검사 특성이 자동으로 적용됩니다. 다음은 그 예입니다.
public record Product(
[Required] string Name,
[Range(1, 1000)] int Quantity);
개발자는 다음을 통해 유효성 검사 시스템의 동작을 사용자 지정합니다.
- 사용자 지정
[Validation]특성 구현 만들기 -
IValidatableObject복잡한 유효성 검사 논리에 대한 인터페이스 구현
유효성 검사에 실패하면 런타임은 유효성 검사 오류에 대한 세부 정보가 포함된 400 - 잘못된 요청 응답을 반환합니다.
최소 API에 대한 기본 제공 유효성 검사 지원 사용
애플리케이션의 서비스 컨테이너에 필요한 서비스를 등록하기 위해 확장 메서드를 호출 AddValidation 하여 최소 API에 대한 기본 제공 유효성 검사 지원을 사용하도록 설정합니다.
builder.Services.AddValidation();
이 구현은 최소 API 처리기 또는 최소 API 처리기에 정의된 형식의 기본 형식으로 정의된 형식을 자동으로 검색합니다. 엔드포인트 필터는 이러한 형식에 대한 유효성 검사를 수행하고 각 엔드포인트에 대해 추가됩니다.
다음 예제와 같이 확장 메서드를 사용하여 특정 엔드포인트에 DisableValidation 대해 유효성 검사를 사용하지 않도록 설정할 수 있습니다.
app.MapPost("/products",
([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
=> TypedResults.Ok(productId))
.DisableValidation();
IProblemDetailsService를 사용하여 유효성 검사 오류 응답 사용자 지정
구현을 사용하여 최소 API 유효성 검사 논리 IProblemDetailsService 의 오류 응답을 사용자 지정합니다. 보다 일관되고 사용자별 오류 응답을 사용하도록 애플리케이션의 서비스 컬렉션에 이 서비스를 등록합니다. 최소 API 유효성 검사에 대한 지원은 .NET 10의 ASP.NET Core에서 도입되었습니다.
사용자 지정 유효성 검사 오류 응답을 구현하려면 다음을 수행합니다.
- 기본 구현 구현 IProblemDetailsService 또는 사용
- DI 컨테이너에 서비스 등록
- 유효성 검사 시스템은 자동으로 등록된 서비스를 사용하여 유효성 검사 오류 응답의 형식을 지정합니다.
IProblemDetailsService를 사용하여 유효성 검사 오류 응답을 사용자 지정하는 방법에 대한 자세한 내용은 최소 API 애플리케이션에서 응답 만들기를 참조하세요.
Responses
경로 처리기는 다음과 같은 형식의 반환 값을 지원합니다.
-
IResult기반 - 여기에는Task<IResult>및ValueTask<IResult>가 포함됩니다. -
string- 여기에는Task<string>및ValueTask<string>이 포함됩니다. -
T(다른 모든 형식) - 여기에는Task<T>및ValueTask<T>가 포함됩니다.
| 반환 값 | Behavior | Content-Type |
|---|---|---|
IResult |
프레임워크가 IResult.ExecuteAsync를 호출합니다. |
IResult 구현에 의해 결정됩니다. |
string |
프레임워크가 응답에 직접 문자열을 씁니다. | text/plain |
T(기타 형식) |
프레임워크 JSON은 응답을 직렬화합니다. | application/json |
처리기 반환 값을 라우팅하는 자세한 가이드는 최소 API 애플리케이션에서 응답 만들기를 참조하세요.
예시 반환 값
문자열 반환 값
app.MapGet("/hello", () => "Hello World");
JSON 반환 값
app.MapGet("/hello", () => new { Message = "Hello World" });
TypedResults 반환
다음 코드는 다음을 반환합니다.TypedResults
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
IResult 반환 값
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
다음 예는 기본 제공 결과 형식을 사용하여 응답을 사용자 지정합니다.
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
사용자 지정 상태 코드
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
기본 제공 결과
일반적인 결과 도우미는 정적 클래스 및 Results 정적 클래스에 TypedResults 있습니다. 반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
헤더 수정
HttpResponse 개체를 사용하여 응답 헤더를 수정합니다.
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
결과 사용자 지정
애플리케이션은 사용자 지정 IResult 형식을 구현하여 응답을 제어할 수 있습니다. 다음 코드는 HTML 결과 형식의 예입니다.
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
이러한 사용자 지정 결과를 더 검색 가능하게 만들려면 Microsoft.AspNetCore.Http.IResultExtensions에 확장 메서드를 추가하는 것이 좋습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
입력된 결과
인터페이스는 IResult 반환된 개체를 HTTP 응답으로 직렬화하는 JSON에 대한 암시적 지원을 활용하지 않는 최소 API에서 반환된 값을 나타낼 수 있습니다. 정적 Results 클래스는 다양한 형식의 응답을 나타내는 다양한 IResult 개체를 만드는 데 사용됩니다. 예를 들어 응답 상태 코드를 설정하거나 다른 URL로 리디렉션합니다.
IResult를 구현하는 형식은 공용이므로 테스트할 때 형식 어설션을 허용합니다. 다음은 그 예입니다.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
정적 TypedResults 클래스에서 해당 메서드의 반환 형식을 확인하여 캐스팅할 올바른 퍼블릭 IResult 형식을 찾을 수 있습니다.
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Filters
자세한 내용은 최소 API 앱의 필터를 참조 하세요.
Authorization
권한 부여 정책을 사용하여 경로를 보호할 수 있습니다. 권한 부여 정책은 [Authorize] 특성을 통해 또는 RequireAuthorization 메서드를 사용하여 선언할 수 있습니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
이전 코드는 RequireAuthorization을 사용하여 작성할 수 있습니다.
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
다음 샘플은 정책 기반 권한 부여를 사용합니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/admin", [Authorize("AdminsOnly")] () =>
"The /admin endpoint is for admins only.");
app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
.RequireAuthorization("AdminsOnly");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용
[AllowAnonymous]는 인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용합니다.
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
경로는 CORS 정책을 사용하여 CORS를 사용할 수 있습니다. CORS는 [EnableCors] 특성을 통해 또는 RequireCors 메서드를 사용하여 선언할 수 있습니다. 다음 샘플은 CORS를 사용하도록 설정합니다.
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
자세한 내용은 ASP.NET Core에서 CORS(원본 간 요청) 사용을 참조하세요.
ValidateScopes 및 ValidateOnBuild
ValidateScopes개발 ValidateOnBuild 환경에서는 기본적으로 사용하도록 설정되지만 다른 환경에서는 사용하지 않도록 설정됩니다.
이 경우 ValidateOnBuild DI 컨테이너는 true빌드 시 서비스 구성의 유효성을 검사합니다. 서비스 구성이 잘못된 경우 서비스가 요청된 런타임이 아닌 앱 시작 시 빌드가 실패합니다.
이 경우 ValidateScopes DI 컨테이너는 true범위가 지정된 서비스가 루트 범위에서 확인되지 않는지 확인합니다. 루트 범위에서 범위가 지정된 서비스를 해결하면 서비스가 요청 범위보다 더 오래 메모리에 유지되므로 메모리 누수가 발생할 수 있습니다.
ValidateScopes 성능 ValidateOnBuild 상의 이유로 비개발 모드에서는 기본적으로 false입니다.
다음 코드는 ValidateScopes 기본적으로 개발 모드에서 사용하도록 설정되어 있지만 릴리스 모드에서는 사용하지 않도록 설정되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
// Intentionally getting service provider from app, not from the request
// This causes an exception from attempting to resolve a scoped service
// outside of a scope.
// Throws System.InvalidOperationException:
// 'Cannot resolve scoped service 'MyScopedService' from root provider.'
var service = app.Services.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved");
});
app.Run();
public class MyScopedService { }
다음 코드는 ValidateOnBuild 기본적으로 개발 모드에서 사용하도록 설정되어 있지만 릴리스 모드에서는 사용하지 않도록 설정되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();
// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
var service = context.RequestServices.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved correctly!");
});
app.Run();
public class MyScopedService { }
public class AnotherService
{
public AnotherService(BrokenService brokenService) { }
}
public class BrokenService { }
다음 코드는 사용 안 함으로 설정됩니다 ValidateScopesValidateOnBuild.Development
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
// Doesn't detect the validation problems because ValidateScopes is false.
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = false;
options.ValidateOnBuild = false;
});
}
참고하십시오
이 문서:
- 최소 API에 대한 빠른 참조를 제공합니다.
- 숙련된 개발자를 대상으로 합니다. 소개는 자습서: ASP.NET Core를 사용하여 최소 API 만들기를 참조 하세요.
최소 API는 다음으로 구성됩니다.
WebApplication
다음 코드는 ASP.NET Core 템플릿에서 생성됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
위의 코드는 명령줄에서 dotnet new web을 통해 만들거나 Visual Studio에서 빈 웹 템플릿을 선택하여 만들 수 있습니다.
다음 코드는 WebApplication를 명시적으로 만들지 않고 app(WebApplicationBuilder)을 만듭니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create는 미리 구성된 기본값을 사용하여 WebApplication 클래스의 새 인스턴스를 초기화합니다.
WebApplication 는 특정 조건에 따라 최소 API 애플리케이션 에 다음 미들웨어를 자동으로 추가합니다.
-
UseDeveloperExceptionPage가HostingEnvironment일 때"Development"가 먼저 추가됩니다. - 사용자 코드가 아직
UseRouting를 호출하지 않은 경우 및 구성된 엔드포인트가 있는 경우(예:UseRouting),app.MapGet가 두 번째로 추가됩니다. -
UseEndpoints는 엔드포인트가 구성된 경우 미들웨어 파이프라인의 끝에 추가됩니다. -
UseAuthentication은 사용자 코드가 아직 호출UseRouting되지 않은 경우와 서비스 공급자에서 검색할 수 있는 경우UseAuthentication즉시IAuthenticationSchemeProvider추가됩니다.IAuthenticationSchemeProvider은AddAuthentication를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. -
UseAuthorization는 사용자 코드가 아직 호출UseAuthorization되지 않았고 서비스 공급자에서 검색할 수 있는 경우IAuthorizationHandlerProvider다음에 추가됩니다.IAuthorizationHandlerProvider은AddAuthorization를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. - 사용자 구성 미들웨어 및 엔드포인트는
UseRouting및UseEndpoints사이에 추가됩니다.
다음 코드는 앱에 추가되는 자동 미들웨어가 생성하는 것입니다.
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
경우에 따라 앱에 대한 기본 미들웨어 구성이 올바르지 않으며 수정이 필요합니다. 예를 들어, UseCors은 UseAuthentication 및 UseAuthorization 전에 호출되어야 합니다. 앱은 호출 UseAuthentication 해야 하며 UseAuthorization 호출되는 경우 UseCors :
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
경로 일치가 발생하기 전에 미들웨어를 실행해야 하는 경우, UseRouting를 호출해야 하며 UseRouting에 대한 호출 전에 미들웨어를 배치해야 합니다.
UseEndpoints 는 앞에서 설명한 대로 자동으로 추가되므로 이 경우에는 필요하지 않습니다.
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
터미널 미들웨어를 추가하는 경우:
- 미들웨어는
UseEndpoints다음에 추가해야 합니다. - 터미널 미들웨어를 올바른 위치에 배치할 수 있도록 앱은
UseRouting및UseEndpoints를 호출해야 합니다.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
터미널 미들웨어는 요청을 처리하는 엔드포인트가 없는 경우 실행되는 미들웨어입니다.
포트 작업
Visual Studio 또는 dotnet new를 사용하여 웹앱을 만들면 앱이 응답하는 포트를 지정하는 Properties/launchSettings.json 파일이 만들어집니다. 다음에 오는 포트 설정 샘플에서는 Visual Studio에서 앱을 실행하면 Unable to connect to web server 'AppName' 오류 대화 상자가 반환됩니다. Visual Studio는 Properties/launchSettings.json에 지정된 포트가 예상되기 때문에 오류를 반환하지만 앱은 app.Run("http://localhost:3000")에서 지정한 포트를 사용하고 있습니다. 명령줄에서 다음 포트 변경 샘플을 실행합니다.
다음 섹션에서는 앱이 응답하는 포트를 설정합니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
위의 코드에서 앱은 포트 3000에 응답합니다.
여러 포트
위의 코드에서 앱은 포트 3000 및 4000에 응답합니다.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
명령줄에서 포트 설정
다음 명령은 앱이 포트 7777에 응답하도록 합니다.
dotnet run --urls="https://localhost:7777"
Kestrel 엔드포인트가 appsettings.json 파일에도 구성된 경우 appsettings.json 파일에서 지정된 URL이 사용됩니다. 자세한 내용은 Kestrel 엔드포인트 구성을 참조하세요.
환경에서 포트 읽기
다음 코드는 환경에서 포트를 읽습니다.
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
환경에서 포트를 설정하는 기본 방법은 다음 섹션에 나오는 ASPNETCORE_URLS 환경 변수를 사용하는 것입니다.
ASPNETCORE_URLS 환경 변수를 통해 포트 설정
ASPNETCORE_URLS 환경 변수를 사용하여 포트를 설정할 수 있습니다.
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS는 여러 URL을 지원합니다.
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
모든 인터페이스에서 수신 대기
다음 샘플은 모든 인터페이스에서의 수신 대기를 보여 줍니다.
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
ASPNETCORE_URLS를 사용하여 모든 인터페이스에서 수신 대기
이전 샘플은 ASPNETCORE_URLS를 사용할 수 있습니다.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
ASPNETCORE_HTTPS_PORTS 사용하여 모든 인터페이스에서 수신 대기
위의 샘플은 사용할 ASPNETCORE_HTTPS_PORTS 수 있습니다.ASPNETCORE_HTTP_PORTS
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
자세한 내용은 ASP.NET Core Kestrel 웹 서버에 대한 엔드포인트 구성을 참조하세요.
개발 인증서로 HTTPS 지정
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
개발 인증서에 대한 자세한 내용은 Windows 및 macOS에서 ASP.NET Core HTTPS 개발 인증서 신뢰를 참조하세요.
사용자 지정 인증서를 사용하여 HTTPS 지정
다음 섹션에서는 파일 및 구성을 통해 사용자 지정 인증서를 지정하는 appsettings.json 방법을 보여 있습니다.
appsettings.json을 사용하여 사용자 지정 인증서 지정
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
구성을 통해 사용자 지정 인증서 지정
var builder = WebApplication.CreateBuilder(args);
// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
인증서 API 사용
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");
httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
환경 읽기
var app = WebApplication.Create(args);
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}
app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");
app.Run();
환경을 사용하는 방법에 대한 자세한 내용은 ASP.NET Core 런타임 환경을 참조하세요.
Configuration
다음 코드는 구성 시스템에서 읽습니다.
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
자세한 내용은 ASP.NET Core의 구성을 참조하세요.
Logging
다음 코드는 로그온 애플리케이션 시작에 메시지를 씁니다.
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
자세한 내용은 .NET 및 ASP.NET Core의 로깅을 참조하세요.
DI(종속성 주입) 컨테이너 액세스
다음 코드에서는 애플리케이션을 시작하는 동안 DI 컨테이너에서 서비스를 가져오는 방법을 보여줍니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();
var app = builder.Build();
app.MapControllers();
using (var scope = app.Services.CreateScope())
{
var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}
app.Run();
다음 코드에서는 특성을 사용하여 DI 컨테이너에서 키에 액세스하는 [FromKeyedServices] 방법을 보여 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
DI에 대한 자세한 내용은 ASP.NET Core의 종속성 주입을 참조 하세요.
WebApplicationBuilder
이 섹션에는 WebApplicationBuilder를 사용하는 샘플 코드가 포함되어 있습니다.
콘텐츠 루트, 애플리케이션 이름, 환경 변경
다음 코드는 콘텐츠 루트, 애플리케이션 이름, 환경을 설정합니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다.
자세한 내용은 ASP.NET Core 기본 사항 개요를 참조하세요
환경 변수 또는 명령줄을 사용하여 콘텐츠 루트, 앱 이름 및 환경 변경
다음 표는 콘텐츠 루트, 앱 이름, 환경 변경에 사용되는 환경 변수와 명령줄 인수를 보여 줍니다.
| feature | 환경 변수 | 명령줄 인수 |
|---|---|---|
| 애플리케이션 이름 | ASPNETCORE_APPLICATIONNAME | --applicationName |
| 환경 이름 | ASPNETCORE_ENVIRONMENT | --environment |
| 콘텐츠 루트 | ASPNETCORE_CONTENTROOT | --contentRoot |
구성 공급자 추가
다음 샘플은 INI 구성 공급자를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
자세한 내용은 ASP.NET Core의 구성에서 파일 구성 공급자를 참조하세요.
구성 읽기
기본적으로 WebApplicationBuilder는 다음을 포함하여 여러 원본에서 구성을 읽습니다.
-
appSettings.json및appSettings.{environment}.json - 환경 변수
- 명령줄
읽은 구성 원본의 전체 목록은 ASP.NET Core의 구성에서 기본 구성을 참조하세요.
다음 코드는 구성에서 HelloKey를 읽고 / 엔드포인트에 값을 표시합니다. 구성 값이 null이면 "Hello"가 message에 할당됩니다.
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
환경 읽기
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
로깅 공급자 추가
var builder = WebApplication.CreateBuilder(args);
// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();
var app = builder.Build();
app.MapGet("/", () => "Hello JSON console!");
app.Run();
서비스 추가
var builder = WebApplication.CreateBuilder(args);
// Add the memory cache services.
builder.Services.AddMemoryCache();
// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();
IHostBuilder 사용자 지정
IHostBuilder의 기존 확장 메서드는 호스트 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
IWebHostBuilder 사용자 지정
IWebHostBuilder의 확장 메서드는 WebApplicationBuilder.WebHost 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
웹 루트 변경
기본적으로 웹 루트는 wwwroot 폴더의 콘텐츠 루트를 기준으로 합니다. 웹 루트는 정적 파일 미들웨어가 정적 파일을 찾는 위치입니다. 웹 루트는 WebHostOptions, 명령줄 또는 UseWebRoot 메서드를 사용하여 변경할 수 있습니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
사용자 지정 DI(종속성 주입) 컨테이너
다음 예제에서는 Autofac을 사용합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
미들웨어 추가
기존 ASP.NET Core 미들웨어는 WebApplication에서 구성할 수 있습니다.
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.
개발자 예외 페이지
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다. 개발자 예외 페이지는 미리 구성된 기본값에서 사용하도록 설정됩니다.
개발 환경에서 다음 코드가 실행되는 경우 /로 이동하면 예외를 보여 주는 친숙한 페이지가 렌더링됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ASP.NET Core 미들웨어 기본 사항
다음 표에는 최소 API와 함께 자주 사용되는 미들웨어 일부가 나와 있습니다.
| Middleware | Description | API |
|---|---|---|
| Authentication | 인증 지원을 제공합니다. | UseAuthentication |
| Authorization | 권한 부여 지원을 제공합니다. | UseAuthorization |
| CORS | 원본 간 리소스 공유를 구성합니다. | UseCors |
| 예외 처리기 | 미들웨어 파이프라인이 throw하는 예외를 전역으로 처리합니다. | UseExceptionHandler |
| 전달된 헤더 | 프록시된 헤더를 현재 요청에 전달합니다. | UseForwardedHeaders |
| HTTPS 리디렉션 | 모든 HTTP 요청을 HTTPS로 리디렉션합니다. | UseHttpsRedirection |
| HSTS(HTTP 엄격한 전송 보안) | 특별한 응답 헤더를 추가하는 보안 향상 미들웨어입니다. | UseHsts |
| 요청 로깅 | HTTP 요청 및 응답 로깅을 지원합니다. | UseHttpLogging |
| 요청 시간 제한 | 요청 시간 제한, 전역 기본값 및 엔드포인트당 구성을 지원합니다. | UseRequestTimeouts |
| W3C 요청 로깅 | W3C 형식의 HTTP 요청 및 응답 로깅을 지원합니다. | UseW3CLogging |
| 응답 캐싱 | 응답 캐시에 대한 지원을 제공합니다. | UseResponseCaching |
| 응답 압축 | 응답 압축에 대한 지원을 제공합니다. | UseResponseCompression |
| Session | 사용자 세션 관리에 대한 지원을 제공합니다. | UseSession |
| 정적 파일 | 정적 파일 및 디렉터리 검색 처리에 대한 지원을 제공합니다. | UseStaticFiles, UseFileServer |
| WebSockets | WebSocket 프로토콜을 활성화합니다. | UseWebSockets |
다음 섹션에서는 요청 처리에 대해 설명합니다. 라우팅, 매개 변수 바인딩 및 응답.
Routing
구성된 WebApplication은 Map{Verb}과 MapMethods를 지원하며 여기서 {Verb}는 Get, Post, Put 또는 Delete 등의 카멜 대/소문자 HTTP 메서드입니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
이러한 메서드에 전달된 Delegate 인수를 "경로 처리기"라고 합니다.
경로 처리기
경로 처리기는 경로가 일치할 때 실행되는 메서드입니다. 경로 처리기는 람다 식, 로컬 함수, 인스턴스 메서드 또는 정적 메서드일 수 있습니다. 경로 처리기는 동기 또는 비동기일 수 있습니다.
람다 식
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
로컬 함수
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
인스턴스 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new HelloHandler();
app.MapGet("/", handler.Hello);
app.Run();
class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}
정적 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
외부에 정의된 엔드포인트 Program.cs
최소 API는 에 Program.cs위치할 필요가 없습니다.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
이 문서의 뒷부분에 있는 경로 그룹도 참조하세요.
명명된 엔드포인트 및 링크 생성
경로의 URL을 생성하기 위해 경로에 이름을 지정할 수 있습니다. 이름이 지정된 경로를 사용하면 앱에서 경로를 하드 코드로 작성할 필요가 없습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
이전 코드는 The link to the hello route is /hello 엔드포인트에서 /를 표시합니다.
참고: 엔드포인트 이름은 대/소문자를 구분합니다.
엔드포인트 이름:
- 전역적으로 고유해야 합니다.
- OpenAPI 지원을 사용할 때 OpenAPI 작업 ID로 사용됩니다. 자세한 내용은 OpenAPI를 참조하세요.
경로 매개 변수
경로 패턴 정의의 일부로 경로 매개 변수를 캡처할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");
app.Run();
이전 코드는 The user id is 3 and book id is 7 URI에서 /users/3/books/7을 반환합니다.
경로 처리기는 캡처할 매개 변수를 선언할 수 있습니다. 캡처하도록 선언된 매개 변수가 있는 경로에 대한 요청이 이루어지면 매개 변수가 구문 분석되어 처리기에 전달됩니다. 이렇게 하면 형식이 안전한 방식으로 값을 쉽게 캡처할 수 있습니다. 이전 코드에서 userId와 bookId는 모두 int입니다.
이전 코드에서 경로 값 중 하나를 int로 변환할 수 없는 경우 예외가 throw됩니다. GET 요청 /users/hello/books/3는 다음 예외를 throw합니다.
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
와일드카드 및 catch all 경로
다음 catch all 경로는 `/posts/hello` 엔드포인트에서 Routing to hello를 반환합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
경로 제약 조건
경로 제약 조건은 경로의 일치 동작을 제한합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");
app.Run();
다음 표는 이전 경로 템플릿과 동작을 보여 줍니다.
| 경로 템플릿 | URI 일치 예제 |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
자세한 내용은 ASP.NET Core의 라우팅에서 경로 제약 조건 참조를 참조하세요.
경로 그룹
MapGroup확장 메서드는 공통 접두사를 사용하여 엔드포인트 그룹을 구성하는 데 도움이 됩니다. 반복 코드를 줄이고 RequireAuthorization를 추가하는 WithMetadata이나 와 같은 메서드로서 단일 호출로 엔드포인트 전체 그룹을 사용자 지정할 수 있게 됩니다.
예를 들어 다음 코드에서는 두 개의 유사한 엔드포인트 그룹을 만듭니다.
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
이 시나리오에서는 Location 결과에서 201 Created 헤더에 대한 상대 주소를 사용할 수 있습니다.
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
첫 번째 엔드포인트 그룹은 접두사로 지정된 요청만 /public/todos 매칭하게 되며 인증 없이 액세스할 수 있습니다. 두 번째 엔드포인트 그룹은 접두사 /private/todos를 가지고 인증이 필요한 요청만 일치합니다.
QueryPrivateTodos
엔드포인트 필터 팩터리는 프라이빗 할 일 데이터에 액세스하고 저장할 수 있도록 경로 처리기의 TodoDb 매개 변수를 수정하는 로컬 함수입니다.
경로 그룹은 경로 매개 변수 및 제약 조건이 있는 중첩 그룹 및 복잡한 접두사 유형도 지원합니다. 다음 예제에서 그룹에 매핑된 user 경로 처리기는 외부 그룹 접두사에 정의된 {org}와 {group} 경로 매개 변수를 캡처할 수 있습니다.
접두사는 비어 있을 수도 있습니다. 이 기능은 경로 패턴을 변경하지 않고 엔드포인트 메타데이터 또는 필터를 엔드포인트 그룹에 추가하는 데 유용할 수 있습니다.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
그룹에 필터 또는 메타데이터를 추가하면 내부 그룹 또는 특정 엔드포인트에 추가될 수 있는 추가 필터 또는 메타데이터를 추가하기 전에 각 엔드포인트에 개별적으로 추가하는 것과 동일한 결과가 됩니다.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
위의 예제에서 외부 필터는 두 번째로 추가된 경우에도 내부 필터 앞에 들어오는 요청을 기록하게 됩니다. 필터가 서로 다른 그룹에 적용되었기 때문에 필터가 추가된 상대적 순서는 중요하지 않습니다. 동일한 그룹 또는 특정 엔드포인트에 적용되는 경우 주문 필터가 추가됩니다.
/outer/inner/에 대한 요청은 다음을 기록합니다.
/outer group filter
/inner group filter
MapGet filter
매개 변수 바인딩
매개 변수 바인딩은 경로 처리기가 표현하는 강력한 형식의 매개 변수로 요청 데이터를 변환하는 프로세스입니다. 바인딩 소스는 매개 변수가 바인딩되는 위치를 결정합니다. 바인딩 소스는 HTTP 메서드 및 매개 변수 형식에 따라 명시적이거나 유추될 수 있습니다.
지원되는 바인딩 소스:
- 경로 값
- 쿼리 문자열
- Header
- 본문(JSON 형식)
- 양식 값
- 종속성 주입에서 제공하는 서비스
- Custom
다음 GET 경로 처리기는 이러한 매개 변수 바인딩 원본 중 일부를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", (int id,
int page,
[FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
Service service) => { });
class Service { }
주요 매개 변수 바인딩 기능
-
명시적 바인딩: , ,
[FromRoute],[FromQuery][FromHeader][FromBody][FromForm]등의 특성을[FromServices]사용하고 바인딩 소스를 명시적으로 지정합니다. -
양식 바인딩:
[FromForm]특성을 사용하여 양식 값을 바인딩하며, 이는IFormFile및IFormFileCollection을 파일 업로드에 지원합니다. - 복합 형식: 양식, 쿼리 문자열 및 헤더의 컬렉션 및 복합 형식에 바인딩합니다.
-
사용자 지정 바인딩:
TryParse,BindAsync또는IBindableFromHttpContext<T>인터페이스를 사용하여 사용자 지정 바인딩 논리를 구현합니다. - 선택적 매개 변수: 선택적 매개 변수에 대해 nullable 형식 및 기본값을 지원합니다.
- 종속성 주입: 매개 변수는 DI 컨테이너에 등록된 서비스에서 자동으로 바인딩됩니다.
-
특수 형식:
HttpContext,HttpRequest,HttpResponse,CancellationToken,ClaimsPrincipal,Stream, 및PipeReader에 대한 자동 바인딩.
더 알아보세요: 고급 시나리오, 유효성 검사, 바인딩 우선 순위 및 문제 해결을 포함한 매개 변수 바인딩에 대한 자세한 내용은 최소 API 애플리케이션의 매개 변수 바인딩을 참조하세요.
Responses
경로 처리기는 다음과 같은 형식의 반환 값을 지원합니다.
-
IResult기반 - 여기에는Task<IResult>및ValueTask<IResult>가 포함됩니다. -
string- 여기에는Task<string>및ValueTask<string>이 포함됩니다. -
T(다른 모든 형식) - 여기에는Task<T>및ValueTask<T>가 포함됩니다.
| 반환 값 | Behavior | Content-Type |
|---|---|---|
IResult |
프레임워크가 IResult.ExecuteAsync를 호출합니다. |
IResult 구현에 의해 결정됩니다. |
string |
프레임워크가 응답에 직접 문자열을 씁니다. | text/plain |
T(기타 형식) |
프레임워크 JSON은 응답을 직렬화합니다. | application/json |
처리기 반환 값을 라우팅하는 자세한 가이드는 최소 API 애플리케이션에서 응답 만들기를 참조하세요.
예시 반환 값
문자열 반환 값
app.MapGet("/hello", () => "Hello World");
JSON 반환 값
app.MapGet("/hello", () => new { Message = "Hello World" });
TypedResults 반환
다음 코드는 다음을 반환합니다.TypedResults
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
IResult 반환 값
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
다음 예는 기본 제공 결과 형식을 사용하여 응답을 사용자 지정합니다.
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
사용자 지정 상태 코드
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
기본 제공 결과
일반적인 결과 도우미는 정적 클래스 및 Results 정적 클래스에 TypedResults 있습니다. 반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
헤더 수정
HttpResponse 개체를 사용하여 응답 헤더를 수정합니다.
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
결과 사용자 지정
애플리케이션은 사용자 지정 IResult 형식을 구현하여 응답을 제어할 수 있습니다. 다음 코드는 HTML 결과 형식의 예입니다.
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
이러한 사용자 지정 결과를 더 검색 가능하게 만들려면 Microsoft.AspNetCore.Http.IResultExtensions에 확장 메서드를 추가하는 것이 좋습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
입력된 결과
인터페이스는 IResult 반환된 개체를 HTTP 응답으로 직렬화하는 JSON에 대한 암시적 지원을 활용하지 않는 최소 API에서 반환된 값을 나타낼 수 있습니다. 정적 Results 클래스는 다양한 형식의 응답을 나타내는 다양한 IResult 개체를 만드는 데 사용됩니다. 예를 들어 응답 상태 코드를 설정하거나 다른 URL로 리디렉션합니다.
IResult를 구현하는 형식은 공용이므로 테스트할 때 형식 어설션을 허용합니다. 다음은 그 예입니다.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
정적 TypedResults 클래스에서 해당 메서드의 반환 형식을 확인하여 캐스팅할 올바른 퍼블릭 IResult 형식을 찾을 수 있습니다.
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Filters
자세한 내용은 최소 API 앱의 필터를 참조 하세요.
Authorization
권한 부여 정책을 사용하여 경로를 보호할 수 있습니다. 권한 부여 정책은 [Authorize] 특성을 통해 또는 RequireAuthorization 메서드를 사용하여 선언할 수 있습니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
이전 코드는 RequireAuthorization을 사용하여 작성할 수 있습니다.
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
다음 샘플은 정책 기반 권한 부여를 사용합니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/admin", [Authorize("AdminsOnly")] () =>
"The /admin endpoint is for admins only.");
app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
.RequireAuthorization("AdminsOnly");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용
[AllowAnonymous]는 인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용합니다.
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
경로는 CORS 정책을 사용하여 CORS를 사용할 수 있습니다. CORS는 [EnableCors] 특성을 통해 또는 RequireCors 메서드를 사용하여 선언할 수 있습니다. 다음 샘플은 CORS를 사용하도록 설정합니다.
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
자세한 내용은 ASP.NET Core에서 CORS(원본 간 요청) 사용을 참조하세요.
ValidateScopes 및 ValidateOnBuild
ValidateScopes개발 ValidateOnBuild 환경에서는 기본적으로 사용하도록 설정되지만 다른 환경에서는 사용하지 않도록 설정됩니다.
이 경우 ValidateOnBuild DI 컨테이너는 true빌드 시 서비스 구성의 유효성을 검사합니다. 서비스 구성이 잘못된 경우 서비스가 요청된 런타임이 아닌 앱 시작 시 빌드가 실패합니다.
이 경우 ValidateScopes DI 컨테이너는 true범위가 지정된 서비스가 루트 범위에서 확인되지 않는지 확인합니다. 루트 범위에서 범위가 지정된 서비스를 해결하면 서비스가 요청 범위보다 더 오래 메모리에 유지되므로 메모리 누수가 발생할 수 있습니다.
ValidateScopes 성능 ValidateOnBuild 상의 이유로 비개발 모드에서는 기본적으로 false입니다.
다음 코드는 ValidateScopes 기본적으로 개발 모드에서 사용하도록 설정되어 있지만 릴리스 모드에서는 사용하지 않도록 설정되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
// Intentionally getting service provider from app, not from the request
// This causes an exception from attempting to resolve a scoped service
// outside of a scope.
// Throws System.InvalidOperationException:
// 'Cannot resolve scoped service 'MyScopedService' from root provider.'
var service = app.Services.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved");
});
app.Run();
public class MyScopedService { }
다음 코드는 ValidateOnBuild 기본적으로 개발 모드에서 사용하도록 설정되어 있지만 릴리스 모드에서는 사용하지 않도록 설정되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();
// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
var service = context.RequestServices.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved correctly!");
});
app.Run();
public class MyScopedService { }
public class AnotherService
{
public AnotherService(BrokenService brokenService) { }
}
public class BrokenService { }
다음 코드는 사용 안 함으로 설정됩니다 ValidateScopesValidateOnBuild.Development
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
// Doesn't detect the validation problems because ValidateScopes is false.
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = false;
options.ValidateOnBuild = false;
});
}
참고하십시오
이 문서:
- 최소 API에 대한 빠른 참조를 제공합니다.
- 숙련된 개발자를 대상으로 합니다. 소개는 자습서: ASP.NET Core를 사용하여 최소 API 만들기를 참조 하세요.
최소 API는 다음으로 구성됩니다.
WebApplication
다음 코드는 ASP.NET Core 템플릿에서 생성됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
위의 코드는 명령줄에서 dotnet new web을 통해 만들거나 Visual Studio에서 빈 웹 템플릿을 선택하여 만들 수 있습니다.
다음 코드는 WebApplication를 명시적으로 만들지 않고 app(WebApplicationBuilder)을 만듭니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create는 미리 구성된 기본값을 사용하여 WebApplication 클래스의 새 인스턴스를 초기화합니다.
WebApplication 는 특정 조건에 따라 최소 API 애플리케이션 에 다음 미들웨어를 자동으로 추가합니다.
-
UseDeveloperExceptionPage가HostingEnvironment일 때"Development"가 먼저 추가됩니다. - 사용자 코드가 아직
UseRouting를 호출하지 않은 경우 및 구성된 엔드포인트가 있는 경우(예:UseRouting),app.MapGet가 두 번째로 추가됩니다. -
UseEndpoints는 엔드포인트가 구성된 경우 미들웨어 파이프라인의 끝에 추가됩니다. -
UseAuthentication은 사용자 코드가 아직 호출UseRouting되지 않은 경우와 서비스 공급자에서 검색할 수 있는 경우UseAuthentication즉시IAuthenticationSchemeProvider추가됩니다.IAuthenticationSchemeProvider은AddAuthentication를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. -
UseAuthorization는 사용자 코드가 아직 호출UseAuthorization되지 않았고 서비스 공급자에서 검색할 수 있는 경우IAuthorizationHandlerProvider다음에 추가됩니다.IAuthorizationHandlerProvider은AddAuthorization를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. - 사용자 구성 미들웨어 및 엔드포인트는
UseRouting및UseEndpoints사이에 추가됩니다.
다음 코드는 앱에 추가되는 자동 미들웨어가 생성하는 것입니다.
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
경우에 따라 앱에 대한 기본 미들웨어 구성이 올바르지 않으며 수정이 필요합니다. 예를 들어, UseCors은 UseAuthentication 및 UseAuthorization 전에 호출되어야 합니다. 앱은 호출 UseAuthentication 해야 하며 UseAuthorization 호출되는 경우 UseCors :
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
경로 일치가 발생하기 전에 미들웨어를 실행해야 하는 경우, UseRouting를 호출해야 하며 UseRouting에 대한 호출 전에 미들웨어를 배치해야 합니다.
UseEndpoints 는 앞에서 설명한 대로 자동으로 추가되므로 이 경우에는 필요하지 않습니다.
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
터미널 미들웨어를 추가하는 경우:
- 미들웨어는
UseEndpoints다음에 추가해야 합니다. - 터미널 미들웨어를 올바른 위치에 배치할 수 있도록 앱은
UseRouting및UseEndpoints를 호출해야 합니다.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
터미널 미들웨어는 요청을 처리하는 엔드포인트가 없는 경우 실행되는 미들웨어입니다.
포트 작업
Visual Studio 또는 dotnet new를 사용하여 웹앱을 만들면 앱이 응답하는 포트를 지정하는 Properties/launchSettings.json 파일이 만들어집니다. 다음에 오는 포트 설정 샘플에서는 Visual Studio에서 앱을 실행하면 Unable to connect to web server 'AppName' 오류 대화 상자가 반환됩니다. Visual Studio는 Properties/launchSettings.json에 지정된 포트가 예상되기 때문에 오류를 반환하지만 앱은 app.Run("http://localhost:3000")에서 지정한 포트를 사용하고 있습니다. 명령줄에서 다음 포트 변경 샘플을 실행합니다.
다음 섹션에서는 앱이 응답하는 포트를 설정합니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
위의 코드에서 앱은 포트 3000에 응답합니다.
여러 포트
위의 코드에서 앱은 포트 3000 및 4000에 응답합니다.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
명령줄에서 포트 설정
다음 명령은 앱이 포트 7777에 응답하도록 합니다.
dotnet run --urls="https://localhost:7777"
Kestrel 엔드포인트가 appsettings.json 파일에도 구성된 경우 appsettings.json 파일에서 지정된 URL이 사용됩니다. 자세한 내용은 Kestrel 엔드포인트 구성을 참조하세요.
환경에서 포트 읽기
다음 코드는 환경에서 포트를 읽습니다.
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
환경에서 포트를 설정하는 기본 방법은 다음 섹션에 나오는 ASPNETCORE_URLS 환경 변수를 사용하는 것입니다.
ASPNETCORE_URLS 환경 변수를 통해 포트 설정
ASPNETCORE_URLS 환경 변수를 사용하여 포트를 설정할 수 있습니다.
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS는 여러 URL을 지원합니다.
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
모든 인터페이스에서 수신 대기
다음 샘플은 모든 인터페이스에서의 수신 대기를 보여 줍니다.
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
ASPNETCORE_URLS를 사용하여 모든 인터페이스에서 수신 대기
이전 샘플은 ASPNETCORE_URLS를 사용할 수 있습니다.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
ASPNETCORE_HTTPS_PORTS 사용하여 모든 인터페이스에서 수신 대기
위의 샘플은 사용할 ASPNETCORE_HTTPS_PORTS 수 있습니다.ASPNETCORE_HTTP_PORTS
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
자세한 내용은 ASP.NET Core Kestrel 웹 서버에 대한 엔드포인트 구성을 참조하세요.
개발 인증서로 HTTPS 지정
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
개발 인증서에 대한 자세한 내용은 Windows 및 macOS에서 ASP.NET Core HTTPS 개발 인증서 신뢰를 참조하세요.
사용자 지정 인증서를 사용하여 HTTPS 지정
다음 섹션에서는 파일 및 구성을 통해 사용자 지정 인증서를 지정하는 appsettings.json 방법을 보여 있습니다.
appsettings.json을 사용하여 사용자 지정 인증서 지정
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
구성을 통해 사용자 지정 인증서 지정
var builder = WebApplication.CreateBuilder(args);
// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
인증서 API 사용
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");
httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
환경 읽기
var app = WebApplication.Create(args);
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}
app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");
app.Run();
환경을 사용하는 방법에 대한 자세한 내용은 ASP.NET Core 런타임 환경을 참조하세요.
Configuration
다음 코드는 구성 시스템에서 읽습니다.
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
자세한 내용은 ASP.NET Core의 구성을 참조하세요.
Logging
다음 코드는 로그온 애플리케이션 시작에 메시지를 씁니다.
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
자세한 내용은 .NET 및 ASP.NET Core의 로깅을 참조하세요.
DI(종속성 주입) 컨테이너 액세스
다음 코드에서는 애플리케이션을 시작하는 동안 DI 컨테이너에서 서비스를 가져오는 방법을 보여줍니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();
var app = builder.Build();
app.MapControllers();
using (var scope = app.Services.CreateScope())
{
var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}
app.Run();
다음 코드에서는 특성을 사용하여 DI 컨테이너에서 키에 액세스하는 [FromKeyedServices] 방법을 보여 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
DI에 대한 자세한 내용은 ASP.NET Core의 종속성 주입을 참조 하세요.
WebApplicationBuilder
이 섹션에는 WebApplicationBuilder를 사용하는 샘플 코드가 포함되어 있습니다.
콘텐츠 루트, 애플리케이션 이름, 환경 변경
다음 코드는 콘텐츠 루트, 애플리케이션 이름, 환경을 설정합니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다.
자세한 내용은 ASP.NET Core 기본 사항 개요를 참조하세요
환경 변수 또는 명령줄을 사용하여 콘텐츠 루트, 앱 이름 및 환경 변경
다음 표는 콘텐츠 루트, 앱 이름, 환경 변경에 사용되는 환경 변수와 명령줄 인수를 보여 줍니다.
| feature | 환경 변수 | 명령줄 인수 |
|---|---|---|
| 애플리케이션 이름 | ASPNETCORE_APPLICATIONNAME | --applicationName |
| 환경 이름 | ASPNETCORE_ENVIRONMENT | --environment |
| 콘텐츠 루트 | ASPNETCORE_CONTENTROOT | --contentRoot |
구성 공급자 추가
다음 샘플은 INI 구성 공급자를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
자세한 내용은 ASP.NET Core의 구성에서 파일 구성 공급자를 참조하세요.
구성 읽기
기본적으로 WebApplicationBuilder는 다음을 포함하여 여러 원본에서 구성을 읽습니다.
-
appSettings.json및appSettings.{environment}.json - 환경 변수
- 명령줄
읽은 구성 원본의 전체 목록은 ASP.NET Core의 구성에서 기본 구성을 참조하세요.
다음 코드는 구성에서 HelloKey를 읽고 / 엔드포인트에 값을 표시합니다. 구성 값이 null이면 "Hello"가 message에 할당됩니다.
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
환경 읽기
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
로깅 공급자 추가
var builder = WebApplication.CreateBuilder(args);
// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();
var app = builder.Build();
app.MapGet("/", () => "Hello JSON console!");
app.Run();
서비스 추가
var builder = WebApplication.CreateBuilder(args);
// Add the memory cache services.
builder.Services.AddMemoryCache();
// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();
IHostBuilder 사용자 지정
IHostBuilder의 기존 확장 메서드는 호스트 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
IWebHostBuilder 사용자 지정
IWebHostBuilder의 확장 메서드는 WebApplicationBuilder.WebHost 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
웹 루트 변경
기본적으로 웹 루트는 wwwroot 폴더의 콘텐츠 루트를 기준으로 합니다. 웹 루트는 정적 파일 미들웨어가 정적 파일을 찾는 위치입니다. 웹 루트는 WebHostOptions, 명령줄 또는 UseWebRoot 메서드를 사용하여 변경할 수 있습니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
사용자 지정 DI(종속성 주입) 컨테이너
다음 예제에서는 Autofac을 사용합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
미들웨어 추가
기존 ASP.NET Core 미들웨어는 WebApplication에서 구성할 수 있습니다.
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.
개발자 예외 페이지
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다. 개발자 예외 페이지는 미리 구성된 기본값에서 사용하도록 설정됩니다.
개발 환경에서 다음 코드가 실행되는 경우 /로 이동하면 예외를 보여 주는 친숙한 페이지가 렌더링됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ASP.NET Core 미들웨어 기본 사항
다음 표에는 최소 API와 함께 자주 사용되는 미들웨어 일부가 나와 있습니다.
| Middleware | Description | API |
|---|---|---|
| Authentication | 인증 지원을 제공합니다. | UseAuthentication |
| Authorization | 권한 부여 지원을 제공합니다. | UseAuthorization |
| CORS | 원본 간 리소스 공유를 구성합니다. | UseCors |
| 예외 처리기 | 미들웨어 파이프라인이 throw하는 예외를 전역으로 처리합니다. | UseExceptionHandler |
| 전달된 헤더 | 프록시된 헤더를 현재 요청에 전달합니다. | UseForwardedHeaders |
| HTTPS 리디렉션 | 모든 HTTP 요청을 HTTPS로 리디렉션합니다. | UseHttpsRedirection |
| HSTS(HTTP 엄격한 전송 보안) | 특별한 응답 헤더를 추가하는 보안 향상 미들웨어입니다. | UseHsts |
| 요청 로깅 | HTTP 요청 및 응답 로깅을 지원합니다. | UseHttpLogging |
| 요청 시간 제한 | 요청 시간 제한, 전역 기본값 및 엔드포인트당 구성을 지원합니다. | UseRequestTimeouts |
| W3C 요청 로깅 | W3C 형식의 HTTP 요청 및 응답 로깅을 지원합니다. | UseW3CLogging |
| 응답 캐싱 | 응답 캐시에 대한 지원을 제공합니다. | UseResponseCaching |
| 응답 압축 | 응답 압축에 대한 지원을 제공합니다. | UseResponseCompression |
| Session | 사용자 세션 관리에 대한 지원을 제공합니다. | UseSession |
| 정적 파일 | 정적 파일 및 디렉터리 검색 처리에 대한 지원을 제공합니다. | UseStaticFiles, UseFileServer |
| WebSockets | WebSocket 프로토콜을 활성화합니다. | UseWebSockets |
다음 섹션에서는 요청 처리에 대해 설명합니다. 라우팅, 매개 변수 바인딩 및 응답.
Routing
구성된 WebApplication은 Map{Verb}과 MapMethods를 지원하며 여기서 {Verb}는 Get, Post, Put 또는 Delete 등의 카멜 대/소문자 HTTP 메서드입니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
이러한 메서드에 전달된 Delegate 인수를 "경로 처리기"라고 합니다.
경로 처리기
경로 처리기는 경로가 일치할 때 실행되는 메서드입니다. 경로 처리기는 람다 식, 로컬 함수, 인스턴스 메서드 또는 정적 메서드일 수 있습니다. 경로 처리기는 동기 또는 비동기일 수 있습니다.
람다 식
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
로컬 함수
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
인스턴스 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new HelloHandler();
app.MapGet("/", handler.Hello);
app.Run();
class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}
정적 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
외부에 정의된 엔드포인트 Program.cs
최소 API는 에 Program.cs위치할 필요가 없습니다.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
이 문서의 뒷부분에 있는 경로 그룹도 참조하세요.
명명된 엔드포인트 및 링크 생성
경로의 URL을 생성하기 위해 경로에 이름을 지정할 수 있습니다. 이름이 지정된 경로를 사용하면 앱에서 경로를 하드 코드로 작성할 필요가 없습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
이전 코드는 The link to the hello route is /hello 엔드포인트에서 /를 표시합니다.
참고: 엔드포인트 이름은 대/소문자를 구분합니다.
엔드포인트 이름:
- 전역적으로 고유해야 합니다.
- OpenAPI 지원을 사용할 때 OpenAPI 작업 ID로 사용됩니다. 자세한 내용은 OpenAPI를 참조하세요.
경로 매개 변수
경로 패턴 정의의 일부로 경로 매개 변수를 캡처할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");
app.Run();
이전 코드는 The user id is 3 and book id is 7 URI에서 /users/3/books/7을 반환합니다.
경로 처리기는 캡처할 매개 변수를 선언할 수 있습니다. 캡처하도록 선언된 매개 변수가 있는 경로에 대한 요청이 이루어지면 매개 변수가 구문 분석되어 처리기에 전달됩니다. 이렇게 하면 형식이 안전한 방식으로 값을 쉽게 캡처할 수 있습니다. 이전 코드에서 userId와 bookId는 모두 int입니다.
이전 코드에서 경로 값 중 하나를 int로 변환할 수 없는 경우 예외가 throw됩니다. GET 요청 /users/hello/books/3는 다음 예외를 throw합니다.
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
와일드카드 및 catch all 경로
다음 catch all 경로는 `/posts/hello` 엔드포인트에서 Routing to hello를 반환합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
경로 제약 조건
경로 제약 조건은 경로의 일치 동작을 제한합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");
app.Run();
다음 표는 이전 경로 템플릿과 동작을 보여 줍니다.
| 경로 템플릿 | URI 일치 예제 |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
자세한 내용은 ASP.NET Core의 라우팅에서 경로 제약 조건 참조를 참조하세요.
경로 그룹
MapGroup확장 메서드는 공통 접두사를 사용하여 엔드포인트 그룹을 구성하는 데 도움이 됩니다. 반복 코드를 줄이고 RequireAuthorization를 추가하는 WithMetadata이나 와 같은 메서드로서 단일 호출로 엔드포인트 전체 그룹을 사용자 지정할 수 있게 됩니다.
예를 들어 다음 코드에서는 두 개의 유사한 엔드포인트 그룹을 만듭니다.
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
이 시나리오에서는 Location 결과에서 201 Created 헤더에 대한 상대 주소를 사용할 수 있습니다.
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
첫 번째 엔드포인트 그룹은 접두사로 지정된 요청만 /public/todos 매칭하게 되며 인증 없이 액세스할 수 있습니다. 두 번째 엔드포인트 그룹은 접두사 /private/todos를 가지고 인증이 필요한 요청만 일치합니다.
QueryPrivateTodos
엔드포인트 필터 팩터리는 프라이빗 할 일 데이터에 액세스하고 저장할 수 있도록 경로 처리기의 TodoDb 매개 변수를 수정하는 로컬 함수입니다.
경로 그룹은 경로 매개 변수 및 제약 조건이 있는 중첩 그룹 및 복잡한 접두사 유형도 지원합니다. 다음 예제에서 그룹에 매핑된 user 경로 처리기는 외부 그룹 접두사에 정의된 {org}와 {group} 경로 매개 변수를 캡처할 수 있습니다.
접두사는 비어 있을 수도 있습니다. 이 기능은 경로 패턴을 변경하지 않고 엔드포인트 메타데이터 또는 필터를 엔드포인트 그룹에 추가하는 데 유용할 수 있습니다.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
그룹에 필터 또는 메타데이터를 추가하면 내부 그룹 또는 특정 엔드포인트에 추가될 수 있는 추가 필터 또는 메타데이터를 추가하기 전에 각 엔드포인트에 개별적으로 추가하는 것과 동일한 결과가 됩니다.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
위의 예제에서 외부 필터는 두 번째로 추가된 경우에도 내부 필터 앞에 들어오는 요청을 기록하게 됩니다. 필터가 서로 다른 그룹에 적용되었기 때문에 필터가 추가된 상대적 순서는 중요하지 않습니다. 동일한 그룹 또는 특정 엔드포인트에 적용되는 경우 주문 필터가 추가됩니다.
/outer/inner/에 대한 요청은 다음을 기록합니다.
/outer group filter
/inner group filter
MapGet filter
매개 변수 바인딩
매개 변수 바인딩은 경로 처리기가 표현하는 강력한 형식의 매개 변수로 요청 데이터를 변환하는 프로세스입니다. 바인딩 소스는 매개 변수가 바인딩되는 위치를 결정합니다. 바인딩 소스는 HTTP 메서드 및 매개 변수 형식에 따라 명시적이거나 유추될 수 있습니다.
지원되는 바인딩 소스:
- 경로 값
- 쿼리 문자열
- Header
- 본문(JSON 형식)
- 양식 값
- 종속성 주입에서 제공하는 서비스
- Custom
다음 GET 경로 처리기는 이러한 매개 변수 바인딩 원본 중 일부를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", (int id,
int page,
[FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
Service service) => { });
class Service { }
주요 매개 변수 바인딩 기능
-
명시적 바인딩: , ,
[FromRoute],[FromQuery][FromHeader][FromBody][FromForm]등의 특성을[FromServices]사용하고 바인딩 소스를 명시적으로 지정합니다. -
양식 바인딩:
[FromForm]특성을 사용하여 양식 값을 바인딩하며, 이는IFormFile및IFormFileCollection을 파일 업로드에 지원합니다. - 복합 형식: 양식, 쿼리 문자열 및 헤더의 컬렉션 및 복합 형식에 바인딩합니다.
-
사용자 지정 바인딩:
TryParse,BindAsync또는IBindableFromHttpContext<T>인터페이스를 사용하여 사용자 지정 바인딩 논리를 구현합니다. - 선택적 매개 변수: 선택적 매개 변수에 대해 nullable 형식 및 기본값을 지원합니다.
- 종속성 주입: 매개 변수는 DI 컨테이너에 등록된 서비스에서 자동으로 바인딩됩니다.
-
특수 형식:
HttpContext,HttpRequest,HttpResponse,CancellationToken,ClaimsPrincipal,Stream, 및PipeReader에 대한 자동 바인딩.
더 알아보세요: 고급 시나리오, 유효성 검사, 바인딩 우선 순위 및 문제 해결을 포함한 매개 변수 바인딩에 대한 자세한 내용은 최소 API 애플리케이션의 매개 변수 바인딩을 참조하세요.
Responses
경로 처리기는 다음과 같은 형식의 반환 값을 지원합니다.
-
IResult기반 - 여기에는Task<IResult>및ValueTask<IResult>가 포함됩니다. -
string- 여기에는Task<string>및ValueTask<string>이 포함됩니다. -
T(다른 모든 형식) - 여기에는Task<T>및ValueTask<T>가 포함됩니다.
| 반환 값 | Behavior | Content-Type |
|---|---|---|
IResult |
프레임워크가 IResult.ExecuteAsync를 호출합니다. |
IResult 구현에 의해 결정됩니다. |
string |
프레임워크가 응답에 직접 문자열을 씁니다. | text/plain |
T(기타 형식) |
프레임워크 JSON은 응답을 직렬화합니다. | application/json |
처리기 반환 값을 라우팅하는 자세한 가이드는 최소 API 애플리케이션에서 응답 만들기를 참조하세요.
예시 반환 값
문자열 반환 값
app.MapGet("/hello", () => "Hello World");
JSON 반환 값
app.MapGet("/hello", () => new { Message = "Hello World" });
TypedResults 반환
다음 코드는 다음을 반환합니다.TypedResults
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
IResult 반환 값
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
다음 예는 기본 제공 결과 형식을 사용하여 응답을 사용자 지정합니다.
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
사용자 지정 상태 코드
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
기본 제공 결과
일반적인 결과 도우미는 정적 클래스 및 Results 정적 클래스에 TypedResults 있습니다. 반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
헤더 수정
HttpResponse 개체를 사용하여 응답 헤더를 수정합니다.
app.MapGet("/", (HttpContext context) => {
// Set a custom header
context.Response.Headers["X-Custom-Header"] = "CustomValue";
// Set a known header
context.Response.Headers.CacheControl = $"public,max-age=3600";
return "Hello World";
});
결과 사용자 지정
애플리케이션은 사용자 지정 IResult 형식을 구현하여 응답을 제어할 수 있습니다. 다음 코드는 HTML 결과 형식의 예입니다.
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
이러한 사용자 지정 결과를 더 검색 가능하게 만들려면 Microsoft.AspNetCore.Http.IResultExtensions에 확장 메서드를 추가하는 것이 좋습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
입력된 결과
인터페이스는 IResult 반환된 개체를 HTTP 응답으로 직렬화하는 JSON에 대한 암시적 지원을 활용하지 않는 최소 API에서 반환된 값을 나타낼 수 있습니다. 정적 Results 클래스는 다양한 형식의 응답을 나타내는 다양한 IResult 개체를 만드는 데 사용됩니다. 예를 들어 응답 상태 코드를 설정하거나 다른 URL로 리디렉션합니다.
IResult를 구현하는 형식은 공용이므로 테스트할 때 형식 어설션을 허용합니다. 다음은 그 예입니다.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
정적 TypedResults 클래스에서 해당 메서드의 반환 형식을 확인하여 캐스팅할 올바른 퍼블릭 IResult 형식을 찾을 수 있습니다.
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Filters
자세한 내용은 최소 API 앱의 필터를 참조 하세요.
Authorization
권한 부여 정책을 사용하여 경로를 보호할 수 있습니다. 권한 부여 정책은 [Authorize] 특성을 통해 또는 RequireAuthorization 메서드를 사용하여 선언할 수 있습니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
이전 코드는 RequireAuthorization을 사용하여 작성할 수 있습니다.
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
다음 샘플은 정책 기반 권한 부여를 사용합니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/admin", [Authorize("AdminsOnly")] () =>
"The /admin endpoint is for admins only.");
app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
.RequireAuthorization("AdminsOnly");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용
[AllowAnonymous]는 인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용합니다.
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
경로는 CORS 정책을 사용하여 CORS를 사용할 수 있습니다. CORS는 [EnableCors] 특성을 통해 또는 RequireCors 메서드를 사용하여 선언할 수 있습니다. 다음 샘플은 CORS를 사용하도록 설정합니다.
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
자세한 내용은 ASP.NET Core에서 CORS(원본 간 요청) 사용을 참조하세요.
ValidateScopes 및 ValidateOnBuild
ValidateScopes개발 ValidateOnBuild 환경에서는 기본적으로 사용하도록 설정되지만 다른 환경에서는 사용하지 않도록 설정됩니다.
이 경우 ValidateOnBuild DI 컨테이너는 true빌드 시 서비스 구성의 유효성을 검사합니다. 서비스 구성이 잘못된 경우 서비스가 요청된 런타임이 아닌 앱 시작 시 빌드가 실패합니다.
이 경우 ValidateScopes DI 컨테이너는 true범위가 지정된 서비스가 루트 범위에서 확인되지 않는지 확인합니다. 루트 범위에서 범위가 지정된 서비스를 해결하면 서비스가 요청 범위보다 더 오래 메모리에 유지되므로 메모리 누수가 발생할 수 있습니다.
ValidateScopes 성능 ValidateOnBuild 상의 이유로 비개발 모드에서는 기본적으로 false입니다.
다음 코드는 ValidateScopes 기본적으로 개발 모드에서 사용하도록 설정되어 있지만 릴리스 모드에서는 사용하지 않도록 설정되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
// Intentionally getting service provider from app, not from the request
// This causes an exception from attempting to resolve a scoped service
// outside of a scope.
// Throws System.InvalidOperationException:
// 'Cannot resolve scoped service 'MyScopedService' from root provider.'
var service = app.Services.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved");
});
app.Run();
public class MyScopedService { }
다음 코드는 ValidateOnBuild 기본적으로 개발 모드에서 사용하도록 설정되어 있지만 릴리스 모드에서는 사용하지 않도록 설정되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();
// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
}
else
{
Console.WriteLine("Release environment");
}
app.MapGet("/", context =>
{
var service = context.RequestServices.GetRequiredService<MyScopedService>();
return context.Response.WriteAsync("Service resolved correctly!");
});
app.Run();
public class MyScopedService { }
public class AnotherService
{
public AnotherService(BrokenService brokenService) { }
}
public class BrokenService { }
다음 코드는 사용 안 함으로 설정됩니다 ValidateScopesValidateOnBuild.Development
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine("Development environment");
// Doesn't detect the validation problems because ValidateScopes is false.
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = false;
options.ValidateOnBuild = false;
});
}
참고하십시오
이 문서:
- 최소 API에 대한 빠른 참조를 제공합니다.
- 숙련된 개발자를 대상으로 합니다. 소개는 자습서: ASP.NET Core를 사용하여 최소 API 만들기를 참조하세요.
최소 API는 다음으로 구성됩니다.
WebApplication
다음 코드는 ASP.NET Core 템플릿에서 생성됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
위의 코드는 명령줄에서 dotnet new web을 통해 만들거나 Visual Studio에서 빈 웹 템플릿을 선택하여 만들 수 있습니다.
다음 코드는 WebApplication를 명시적으로 만들지 않고 app(WebApplicationBuilder)을 만듭니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create는 미리 구성된 기본값을 사용하여 WebApplication 클래스의 새 인스턴스를 초기화합니다.
WebApplication 는 특정 조건에 따라 최소 API 애플리케이션 에 다음 미들웨어를 자동으로 추가합니다.
-
UseDeveloperExceptionPage가HostingEnvironment일 때"Development"가 먼저 추가됩니다. - 사용자 코드가 아직
UseRouting를 호출하지 않은 경우 및 구성된 엔드포인트가 있는 경우(예:UseRouting),app.MapGet가 두 번째로 추가됩니다. -
UseEndpoints는 엔드포인트가 구성된 경우 미들웨어 파이프라인의 끝에 추가됩니다. -
UseAuthentication은 사용자 코드가 아직 호출UseRouting되지 않은 경우와 서비스 공급자에서 검색할 수 있는 경우UseAuthentication즉시IAuthenticationSchemeProvider추가됩니다.IAuthenticationSchemeProvider은AddAuthentication를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. -
UseAuthorization는 사용자 코드가 아직 호출UseAuthorization되지 않았고 서비스 공급자에서 검색할 수 있는 경우IAuthorizationHandlerProvider다음에 추가됩니다.IAuthorizationHandlerProvider은AddAuthorization를 사용할 때 기본적으로 추가되고IServiceProviderIsService를 사용하여 서비스가 검색됩니다. - 사용자 구성 미들웨어 및 엔드포인트는
UseRouting및UseEndpoints사이에 추가됩니다.
다음 코드는 앱에 추가되는 자동 미들웨어가 생성하는 것입니다.
if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
if (isAuthenticationConfigured)
{
app.UseAuthentication();
}
if (isAuthorizationConfigured)
{
app.UseAuthorization();
}
// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints
app.UseEndpoints(e => {});
경우에 따라 앱에 대한 기본 미들웨어 구성이 올바르지 않으며 수정이 필요합니다. 예를 들어, UseCors은 UseAuthentication 및 UseAuthorization 전에 호출되어야 합니다. 앱은 호출 UseAuthentication 해야 하며 UseAuthorization 호출되는 경우 UseCors :
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
경로 일치가 발생하기 전에 미들웨어를 실행해야 하는 경우, UseRouting를 호출해야 하며 UseRouting에 대한 호출 전에 미들웨어를 배치해야 합니다.
UseEndpoints 는 앞에서 설명한 대로 자동으로 추가되므로 이 경우에는 필요하지 않습니다.
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
터미널 미들웨어를 추가하는 경우:
- 미들웨어는
UseEndpoints다음에 추가해야 합니다. - 터미널 미들웨어를 올바른 위치에 배치할 수 있도록 앱은
UseRouting및UseEndpoints를 호출해야 합니다.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
터미널 미들웨어는 요청을 처리하는 엔드포인트가 없는 경우 실행되는 미들웨어입니다.
포트 작업
Visual Studio 또는 dotnet new를 사용하여 웹앱을 만들면 앱이 응답하는 포트를 지정하는 Properties/launchSettings.json 파일이 만들어집니다. 다음에 오는 포트 설정 샘플에서는 Visual Studio에서 앱을 실행하면 Unable to connect to web server 'AppName' 오류 대화 상자가 반환됩니다. Visual Studio는 Properties/launchSettings.json에 지정된 포트가 예상되기 때문에 오류를 반환하지만 앱은 app.Run("http://localhost:3000")에서 지정한 포트를 사용하고 있습니다. 명령줄에서 다음 포트 변경 샘플을 실행합니다.
다음 섹션에서는 앱이 응답하는 포트를 설정합니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
위의 코드에서 앱은 포트 3000에 응답합니다.
여러 포트
위의 코드에서 앱은 포트 3000 및 4000에 응답합니다.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
명령줄에서 포트 설정
다음 명령은 앱이 포트 7777에 응답하도록 합니다.
dotnet run --urls="https://localhost:7777"
Kestrel 엔드포인트가 appsettings.json 파일에도 구성된 경우 appsettings.json 파일에서 지정된 URL이 사용됩니다. 자세한 내용은 Kestrel 엔드포인트 구성을 참조하세요.
환경에서 포트 읽기
다음 코드는 환경에서 포트를 읽습니다.
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
환경에서 포트를 설정하는 기본 방법은 다음 섹션에 나오는 ASPNETCORE_URLS 환경 변수를 사용하는 것입니다.
ASPNETCORE_URLS 환경 변수를 통해 포트 설정
ASPNETCORE_URLS 환경 변수를 사용하여 포트를 설정할 수 있습니다.
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS는 여러 URL을 지원합니다.
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
환경을 사용하는 방법에 대한 자세한 내용은 ASP.NET Core 런타임 환경을 참조하세요.
모든 인터페이스에서 수신 대기
다음 샘플은 모든 인터페이스에서의 수신 대기를 보여 줍니다.
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
ASPNETCORE_URLS를 사용하여 모든 인터페이스에서 수신 대기
이전 샘플은 ASPNETCORE_URLS를 사용할 수 있습니다.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
개발 인증서로 HTTPS 지정
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
개발 인증서에 대한 자세한 내용은 Windows 및 macOS에서 ASP.NET Core HTTPS 개발 인증서 신뢰를 참조하세요.
사용자 지정 인증서를 사용하여 HTTPS 지정
다음 섹션에서는 파일 및 구성을 통해 사용자 지정 인증서를 지정하는 appsettings.json 방법을 보여 있습니다.
appsettings.json을 사용하여 사용자 지정 인증서 지정
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
구성을 통해 사용자 지정 인증서 지정
var builder = WebApplication.CreateBuilder(args);
// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
인증서 API 사용
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");
httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Configuration
다음 코드는 구성 시스템에서 읽습니다.
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
자세한 내용은 ASP.NET Core의 구성을 참조하세요.
Logging
다음 코드는 로그온 애플리케이션 시작에 메시지를 씁니다.
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
자세한 내용은 .NET 및 ASP.NET Core의 로깅을 참조하세요.
DI(종속성 주입) 컨테이너 액세스
다음 코드에서는 애플리케이션을 시작하는 동안 DI 컨테이너에서 서비스를 가져오는 방법을 보여줍니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();
var app = builder.Build();
app.MapControllers();
using (var scope = app.Services.CreateScope())
{
var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}
app.Run();
자세한 내용은 ASP.NET Core에서 종속성 주입을 참조하세요.
WebApplicationBuilder
이 섹션에는 WebApplicationBuilder를 사용하는 샘플 코드가 포함되어 있습니다.
콘텐츠 루트, 애플리케이션 이름, 환경 변경
다음 코드는 콘텐츠 루트, 애플리케이션 이름, 환경을 설정합니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다.
자세한 내용은 ASP.NET Core 기본 사항 개요를 참조하세요
환경 변수 또는 명령줄을 통해 콘텐츠 루트, 앱 이름, 환경 변경
다음 표는 콘텐츠 루트, 앱 이름, 환경 변경에 사용되는 환경 변수와 명령줄 인수를 보여 줍니다.
| feature | 환경 변수 | 명령줄 인수 |
|---|---|---|
| 애플리케이션 이름 | ASPNETCORE_APPLICATIONNAME | --applicationName |
| 환경 이름 | ASPNETCORE_ENVIRONMENT | --environment |
| 콘텐츠 루트 | ASPNETCORE_CONTENTROOT | --contentRoot |
구성 공급자 추가
다음 샘플은 INI 구성 공급자를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
자세한 내용은 ASP.NET Core의 구성에서 파일 구성 공급자를 참조하세요.
구성 읽기
기본적으로 WebApplicationBuilder는 다음을 포함하여 여러 원본에서 구성을 읽습니다.
-
appSettings.json및appSettings.{environment}.json - 환경 변수
- 명령줄
다음 코드는 구성에서 HelloKey를 읽고 / 엔드포인트에 값을 표시합니다. 구성 값이 null이면 "Hello"가 message에 할당됩니다.
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
구성 소스 읽기의 전체 목록은 ASP.NET Core의 구성에서 기본 구성을 참조하세요.
로깅 공급자 추가
var builder = WebApplication.CreateBuilder(args);
// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();
var app = builder.Build();
app.MapGet("/", () => "Hello JSON console!");
app.Run();
서비스 추가
var builder = WebApplication.CreateBuilder(args);
// Add the memory cache services.
builder.Services.AddMemoryCache();
// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();
IHostBuilder 사용자 지정
IHostBuilder의 기존 확장 메서드는 호스트 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
IWebHostBuilder 사용자 지정
IWebHostBuilder의 확장 메서드는 WebApplicationBuilder.WebHost 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
웹 루트 변경
기본적으로 웹 루트는 wwwroot 폴더의 콘텐츠 루트를 기준으로 합니다. 웹 루트는 정적 파일 미들웨어가 정적 파일을 찾는 위치입니다. 웹 루트는 WebHostOptions, 명령줄 또는 UseWebRoot 메서드를 사용하여 변경할 수 있습니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
사용자 지정 DI(종속성 주입) 컨테이너
다음 예제에서는 Autofac을 사용합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
미들웨어 추가
기존 ASP.NET Core 미들웨어는 WebApplication에서 구성할 수 있습니다.
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.
개발자 예외 페이지
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다. 개발자 예외 페이지는 미리 구성된 기본값에서 사용하도록 설정됩니다.
개발 환경에서 다음 코드가 실행되는 경우 /로 이동하면 예외를 보여 주는 친숙한 페이지가 렌더링됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ASP.NET Core 미들웨어 기본 사항
다음 표에는 최소 API와 함께 자주 사용되는 미들웨어 일부가 나와 있습니다.
| Middleware | Description | API |
|---|---|---|
| Authentication | 인증 지원을 제공합니다. | UseAuthentication |
| Authorization | 권한 부여 지원을 제공합니다. | UseAuthorization |
| CORS | 원본 간 리소스 공유를 구성합니다. | UseCors |
| 예외 처리기 | 미들웨어 파이프라인이 throw하는 예외를 전역으로 처리합니다. | UseExceptionHandler |
| 전달된 헤더 | 프록시된 헤더를 현재 요청에 전달합니다. | UseForwardedHeaders |
| HTTPS 리디렉션 | 모든 HTTP 요청을 HTTPS로 리디렉션합니다. | UseHttpsRedirection |
| HSTS(HTTP 엄격한 전송 보안) | 특별한 응답 헤더를 추가하는 보안 향상 미들웨어입니다. | UseHsts |
| 요청 로깅 | HTTP 요청 및 응답 로깅을 지원합니다. | UseHttpLogging |
| 요청 시간 제한 | 요청 시간 제한, 전역 기본값 및 엔드포인트당 구성을 지원합니다. | UseRequestTimeouts |
| W3C 요청 로깅 | W3C 형식의 HTTP 요청 및 응답 로깅을 지원합니다. | UseW3CLogging |
| 응답 캐싱 | 응답 캐시에 대한 지원을 제공합니다. | UseResponseCaching |
| 응답 압축 | 응답 압축에 대한 지원을 제공합니다. | UseResponseCompression |
| Session | 사용자 세션 관리에 대한 지원을 제공합니다. | UseSession |
| 정적 파일 | 정적 파일 및 디렉터리 검색 처리에 대한 지원을 제공합니다. | UseStaticFiles, UseFileServer |
| WebSockets | WebSocket 프로토콜을 활성화합니다. | UseWebSockets |
다음 섹션에서는 요청 처리에 대해 설명합니다. 라우팅, 매개 변수 바인딩 및 응답.
Routing
구성된 WebApplication은 Map{Verb}과 MapMethods를 지원하며 여기서 {Verb}는 Get, Post, Put 또는 Delete 등의 카멜 대/소문자 HTTP 메서드입니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
이러한 메서드에 전달된 Delegate 인수를 "경로 처리기"라고 합니다.
경로 처리기
경로 처리기는 경로가 일치할 때 실행되는 메서드입니다. 경로 처리기는 람다 식, 로컬 함수, 인스턴스 메서드 또는 정적 메서드일 수 있습니다. 경로 처리기는 동기 또는 비동기일 수 있습니다.
람다 식
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
로컬 함수
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
인스턴스 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new HelloHandler();
app.MapGet("/", handler.Hello);
app.Run();
class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}
정적 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
외부에 정의된 엔드포인트 Program.cs
최소 API는 에 Program.cs위치할 필요가 없습니다.
Program.cs
using MinAPISeparateFile;
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
TodoEndpoints.Map(app);
app.Run();
TodoEndpoints.cs
namespace MinAPISeparateFile;
public static class TodoEndpoints
{
public static void Map(WebApplication app)
{
app.MapGet("/", async context =>
{
// Get all todo items
await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
});
app.MapGet("/{id}", async context =>
{
// Get one todo item
await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
});
}
}
이 문서의 뒷부분에 있는 경로 그룹도 참조하세요.
명명된 엔드포인트 및 링크 생성
경로의 URL을 생성하기 위해 경로에 이름을 지정할 수 있습니다. 이름이 지정된 경로를 사용하면 앱에서 경로를 하드 코드로 작성할 필요가 없습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
이전 코드는 The link to the hello route is /hello 엔드포인트에서 /를 표시합니다.
참고: 엔드포인트 이름은 대/소문자를 구분합니다.
엔드포인트 이름:
- 전역적으로 고유해야 합니다.
- OpenAPI 지원을 사용할 때 OpenAPI 작업 ID로 사용됩니다. 자세한 내용은 OpenAPI를 참조하세요.
경로 매개 변수
경로 패턴 정의의 일부로 경로 매개 변수를 캡처할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");
app.Run();
이전 코드는 The user id is 3 and book id is 7 URI에서 /users/3/books/7을 반환합니다.
경로 처리기는 캡처할 매개 변수를 선언할 수 있습니다. 캡처하도록 선언된 매개 변수가 있는 경로에 대한 요청이 이루어지면 매개 변수가 구문 분석되어 처리기에 전달됩니다. 이렇게 하면 형식이 안전한 방식으로 값을 쉽게 캡처할 수 있습니다. 이전 코드에서 userId와 bookId는 모두 int입니다.
이전 코드에서 경로 값 중 하나를 int로 변환할 수 없는 경우 예외가 throw됩니다. GET 요청 /users/hello/books/3는 다음 예외를 throw합니다.
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
와일드카드 및 catch all 경로
다음 catch all 경로는 `/posts/hello` 엔드포인트에서 Routing to hello를 반환합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
경로 제약 조건
경로 제약 조건은 경로의 일치 동작을 제한합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");
app.Run();
다음 표는 이전 경로 템플릿과 동작을 보여 줍니다.
| 경로 템플릿 | URI 일치 예제 |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
자세한 내용은 ASP.NET Core의 라우팅에서 경로 제약 조건 참조를 참조하세요.
경로 그룹
MapGroup확장 메서드는 공통 접두사를 사용하여 엔드포인트 그룹을 구성하는 데 도움이 됩니다. 반복 코드를 줄이고 RequireAuthorization를 추가하는 WithMetadata이나 와 같은 메서드로서 단일 호출로 엔드포인트 전체 그룹을 사용자 지정할 수 있게 됩니다.
예를 들어 다음 코드에서는 두 개의 유사한 엔드포인트 그룹을 만듭니다.
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
이 시나리오에서는 Location 결과에서 201 Created 헤더에 대한 상대 주소를 사용할 수 있습니다.
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
첫 번째 엔드포인트 그룹은 접두사로 지정된 요청만 /public/todos 매칭하게 되며 인증 없이 액세스할 수 있습니다. 두 번째 엔드포인트 그룹은 접두사 /private/todos를 가지고 인증이 필요한 요청만 일치합니다.
QueryPrivateTodos
엔드포인트 필터 팩터리는 프라이빗 할 일 데이터에 액세스하고 저장할 수 있도록 경로 처리기의 TodoDb 매개 변수를 수정하는 로컬 함수입니다.
경로 그룹은 경로 매개 변수 및 제약 조건이 있는 중첩 그룹 및 복잡한 접두사 유형도 지원합니다. 다음 예제에서 그룹에 매핑된 user 경로 처리기는 외부 그룹 접두사에 정의된 {org}와 {group} 경로 매개 변수를 캡처할 수 있습니다.
접두사는 비어 있을 수도 있습니다. 이 기능은 경로 패턴을 변경하지 않고 엔드포인트 메타데이터 또는 필터를 엔드포인트 그룹에 추가하는 데 유용할 수 있습니다.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
그룹에 필터 또는 메타데이터를 추가하면 내부 그룹 또는 특정 엔드포인트에 추가될 수 있는 추가 필터 또는 메타데이터를 추가하기 전에 각 엔드포인트에 개별적으로 추가하는 것과 동일한 결과가 됩니다.
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
위의 예제에서 외부 필터는 두 번째로 추가된 경우에도 내부 필터 앞에 들어오는 요청을 기록하게 됩니다. 필터가 서로 다른 그룹에 적용되었기 때문에 필터가 추가된 상대적 순서는 중요하지 않습니다. 동일한 그룹 또는 특정 엔드포인트에 적용되는 경우 주문 필터가 추가됩니다.
/outer/inner/에 대한 요청은 다음을 기록합니다.
/outer group filter
/inner group filter
MapGet filter
매개 변수 바인딩
매개 변수 바인딩은 경로 처리기가 표현하는 강력한 형식의 매개 변수로 요청 데이터를 변환하는 프로세스입니다. 바인딩 소스는 매개 변수가 바인딩되는 위치를 결정합니다. 바인딩 소스는 HTTP 메서드 및 매개 변수 형식에 따라 명시적이거나 유추될 수 있습니다.
지원되는 바인딩 소스:
- 경로 값
- 쿼리 문자열
- Header
- 본문(JSON 형식)
- 종속성 주입에서 제공하는 서비스
- Custom
양식 값의 바인딩은 .NET 6 및 7에서 기본적으로 지원되지 않습니다.
다음 GET 경로 처리기는 이러한 매개 변수 바인딩 소스 중 일부를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", (int id,
int page,
[FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
Service service) => { });
class Service { }
다음 표에서는 이전 예제에서 사용된 매개 변수와 연결된 바인딩 소스 간의 관계를 보여 줍니다.
| Parameter | 바인딩 원본 |
|---|---|
id |
경로 값 |
page |
쿼리 문자열 |
customHeader |
header |
service |
종속성 주입에서 제공 |
HTTP 메서드 GET, HEAD, OPTIONS, DELETE는 본문에서 암시적으로 바인딩되지 않습니다. 이러한 HTTP 메서드에 대해 본문(JSON 형식)에서 바인딩하려면 로 [FromBody]하거나 HttpRequest에서 읽습니다.
다음 예제 POST 경로 처리기는 person 매개 변수에 대해 본문(JSON 형식)의 바인딩 소스를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
이전 예제의 매개 변수는 모두 요청 데이터에서 자동으로 바인딩됩니다. 매개 변수 바인딩이 제공하는 편의를 설명하기 위해 다음 경로 처리기는 요청에서 직접 요청 데이터를 읽는 방법을 보여줍니다.
app.MapGet("/{id}", (HttpRequest request) =>
{
var id = request.RouteValues["id"];
var page = request.Query["page"];
var customHeader = request.Headers["X-CUSTOM-HEADER"];
// ...
});
app.MapPost("/", async (HttpRequest request) =>
{
var person = await request.ReadFromJsonAsync<Person>();
// ...
});
명시적 매개 변수 바인딩
특성을 사용하여 매개 변수가 바인딩되는 위치를 명시적으로 선언할 수 있습니다.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", ([FromRoute] int id,
[FromQuery(Name = "p")] int page,
[FromServices] Service service,
[FromHeader(Name = "Content-Type")] string contentType)
=> {});
class Service { }
record Person(string Name, int Age);
| Parameter | 바인딩 원본 |
|---|---|
id |
이름이 id인 경로 값 |
page |
이름이 "p"인 쿼리 문자열 |
service |
종속성 주입에서 제공 |
contentType |
이름이 "Content-Type"인 헤더 |
Note
양식 값의 바인딩은 .NET 6 및 7에서 기본적으로 지원되지 않습니다.
종속성 주입을 사용하는 매개 변수 바인딩
최소 API에 대한 매개 변수 바인딩은 형식이 서비스로 구성된 경우 종속성 주입을 통해 매개 변수를 바인딩합니다. 매개 변수에 [FromServices] 특성을 명시적으로 적용할 필요는 없습니다. 다음 코드에서는 두 작업 모두 시간을 반환합니다.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
var app = builder.Build();
app.MapGet("/", ( IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();
선택적 매개 변수
경로 처리기에서 선언된 매개 변수는 필수로 처리됩니다.
- 요청이 경로와 일치하는 경우 요청에 모든 필수 매개 변수가 제공되는 경우에만 경로 처리기가 실행됩니다.
- 모든 필수 매개 변수를 제공하지 못하면 오류가 발생합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI | result |
|---|---|
/products?pageNumber=3 |
반환된 3개 |
/products |
BadHttpRequestException: 필수 매개 변수 "int pageNumber"가 쿼리 문자열에서 제공되지 않았습니다. |
/products/1 |
HTTP 404 오류, 일치하는 경로 없음 |
pageNumber를 선택 사항으로 지정하려면 형식을 선택 사항으로 정의하거나 기본값을 제공합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";
app.MapGet("/products2", ListProducts);
app.Run();
| URI | result |
|---|---|
/products?pageNumber=3 |
반환된 3개 |
/products |
반환된 1 |
/products2 |
반환된 1 |
이전 null 허용 값과 기본값은 모든 원본에 적용됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
이전 코드는 요청 본문을 보내지 않은 경우 null 곱을 사용하여 메서드를 호출합니다.
참고: 잘못된 데이터를 제공하고 매개 변수가 null을 허용하면 경로 처리기가 실행되지 않습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI | result |
|---|---|
/products?pageNumber=3 |
3 반환됨 |
/products |
1 반환됨 |
/products?pageNumber=two |
BadHttpRequestException: "2"에서 "Nullable<int> pageNumber" 매개 변수를 바인딩하지 못했습니다. |
/products/two |
HTTP 404 오류, 일치하는 경로 없음 |
자세한 내용은 바인딩 실패 섹션을 참조하세요.
특수 형식
다음 형식은 명시적 특성 없이 바인딩됩니다.
HttpContext: 현재 HTTP 요청 또는 응답에 대한 모든 정보를 포함하는 컨텍스트입니다.
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest 및 HttpResponse: HTTP 요청 및 HTTP 응답:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: 현재 HTTP 요청에 연결된 취소 토큰:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: HttpContext.User에서 바인딩된 요청에 연결된 사용자:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
요청 본문을 Stream 또는 PipeReader로 바인딩
요청 본문을 Stream 또는 PipeReader로 바인딩하여 사용자가 데이터를 처리하여 다음 작업을 수행해야 하는 시나리오를 효율적으로 지원할 수 있습니다.
- 데이터를 Blob 스토리지에 저장하거나 큐 공급자의 큐에 추가합니다.
- 작업자 프로세스 또는 클라우드 함수를 사용하여 저장된 데이터를 처리합니다.
예를 들어 데이터는 Azure Queue Storage의 큐에 추가하거나 Azure Blob Storage에 저장할 수 있습니다.
다음 코드에서는 백그라운드 큐를 구현합니다.
using System.Text.Json;
using System.Threading.Channels;
namespace BackgroundQueueService;
class BackgroundQueue : BackgroundService
{
private readonly Channel<ReadOnlyMemory<byte>> _queue;
private readonly ILogger<BackgroundQueue> _logger;
public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
ILogger<BackgroundQueue> logger)
{
_queue = queue;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
{
try
{
var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
_logger.LogInformation($"{person.Name} is {person.Age} " +
$"years and from {person.Country}");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
}
}
class Person
{
public string Name { get; set; } = String.Empty;
public int Age { get; set; }
public string Country { get; set; } = String.Empty;
}
다음 코드에서는 요청 본문을 Stream에 바인딩합니다.
app.MapPost("/register", async (HttpRequest req, Stream body,
Channel<ReadOnlyMemory<byte>> queue) =>
{
if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
{
return Results.BadRequest();
}
// We're not above the message size and we have a content length, or
// we're a chunked request and we're going to read up to the maxMessageSize + 1.
// We add one to the message size so that we can detect when a chunked request body
// is bigger than our configured max.
var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);
var buffer = new byte[readSize];
// Read at least that many bytes from the body.
var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);
// We read more than the max, so this is a bad request.
if (read > maxMessageSize)
{
return Results.BadRequest();
}
// Attempt to send the buffer to the background queue.
if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
{
return Results.Accepted();
}
// We couldn't accept the message since we're overloaded.
return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});
다음 코드에서는 전체 Program.cs 파일을 보여 줍니다.
using System.Threading.Channels;
using BackgroundQueueService;
var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;
// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;
// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;
// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));
// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();
// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
Channel<ReadOnlyMemory<byte>> queue) =>
{
if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
{
return Results.BadRequest();
}
// We're not above the message size and we have a content length, or
// we're a chunked request and we're going to read up to the maxMessageSize + 1.
// We add one to the message size so that we can detect when a chunked request body
// is bigger than our configured max.
var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);
var buffer = new byte[readSize];
// Read at least that many bytes from the body.
var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);
// We read more than the max, so this is a bad request.
if (read > maxMessageSize)
{
return Results.BadRequest();
}
// Attempt to send the buffer to the background queue.
if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
{
return Results.Accepted();
}
// We couldn't accept the message since we're overloaded.
return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});
app.Run();
- 데이터를 읽을 때
Stream은HttpRequest.Body와 동일한 개체입니다. - 요청 본문은 기본적으로 버퍼링되지 않습니다. 본문을 읽은 후에는 되감을 수 없습니다. 스트림은 여러 번 읽을 수 없습니다.
-
Stream및PipeReader는 최소 작업 처리기 외부에서 사용할 수 없습니다. 기본 버퍼가 삭제되거나 다시 사용되기 때문입니다.
IFormFile 및 IFormFileCollection을 사용하여 파일 업로드
다음 코드는 IFormFile 및 IFormFileCollection을 사용하여 파일을 업로드합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapPost("/upload", async (IFormFile file) =>
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
});
app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
foreach (var file in myFiles)
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
}
});
app.Run();
인증된 파일 업로드 요청은 인증 헤더, 클라이언트 인증서 또는 cookie 헤더를 사용하여 지원됩니다.
.NET 7의 ASP.NET Core에는 위조 방지 기능이 기본적으로 지원되지 않습니다.
위조 방지는 .NET 8 이상의 ASP.NET Core에서 사용할 수 있습니다 . 그러나 IAntiforgery 서비스를 사용하여 구현할 수 있습니다.
헤더 및 쿼리 문자열에서 배열 및 문자열 값 바인딩
다음 코드는 기본 형식의 배열, 문자열 배열 및 StringValues에 쿼리 문자열을 바인딩하는 방법을 보여 줍니다.
// Bind query string values to a primitive type array.
// GET /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
$"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");
// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");
// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");
복합 형식의 배열에 쿼리 문자열 또는 헤더 값 바인딩은 형식에 TryParse가 구현된 경우에 지원됩니다. 다음 코드는 문자열 배열에 바인딩되며 지정된 태그가 있는 모든 항목을 반환합니다.
// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
return await db.Todos
.Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
.ToListAsync();
});
다음 코드는 모델 및 필요한 TryParse 구현을 보여 줍니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
// This is an owned entity.
public Tag Tag { get; set; } = new();
}
[Owned]
public class Tag
{
public string? Name { get; set; } = "n/a";
public static bool TryParse(string? name, out Tag tag)
{
if (name is null)
{
tag = default!;
return false;
}
tag = new Tag { Name = name };
return true;
}
}
다음 코드는 int 배열에 바인딩됩니다.
// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
return await db.Todos
.Where(t => ids.Contains(t.Id))
.ToListAsync();
});
위의 코드를 테스트하려면 다음 엔드포인트를 추가하여 데이터베이스를 Todo 항목으로 채웁니다.
// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
await db.Todos.AddRangeAsync(todos);
await db.SaveChangesAsync();
return Results.Ok(todos);
});
API 테스트 도구를 HttpRepl 사용하여 이전 엔드포인트에 다음 데이터를 전달합니다.
[
{
"id": 1,
"name": "Have Breakfast",
"isComplete": true,
"tag": {
"name": "home"
}
},
{
"id": 2,
"name": "Have Lunch",
"isComplete": true,
"tag": {
"name": "work"
}
},
{
"id": 3,
"name": "Have Supper",
"isComplete": true,
"tag": {
"name": "home"
}
},
{
"id": 4,
"name": "Have Snacks",
"isComplete": true,
"tag": {
"name": "N/A"
}
}
]
다음 코드는 헤더 키 X-Todo-Id에 바인딩되고 Todo 값이 일치하는 Id 항목을 반환합니다.
// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
return await db.Todos
.Where(t => ids.Contains(t.Id))
.ToListAsync();
});
Note
쿼리 문자열에서 바인딩 string[] 할 때 일치하는 쿼리 문자열 값이 없으면 null 값 대신 빈 배열이 생성됩니다.
[AsParameters]를 사용하여 인수 목록에 대한 매개 변수 바인딩
AsParametersAttribute는 유형에 대한 매개 변수 바인딩을 활성화하며 복합 또는 재귀 모델 바인딩은 활성화하지 않습니다.
다음 코드를 생각해 봅시다.
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
// Remaining code removed for brevity.
다음 GET 엔드포인트를 살펴보세요.
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
다음 struct는 강조 표시된 위 매개 변수를 바꾸는 데 사용할 수 있습니다.
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
리팩터링된 GET 엔드포인트는 위의 struct를 AsParameters 특성과 함께 사용합니다.
app.MapGet("/ap/todoitems/{id}",
async ([AsParameters] TodoItemRequest request) =>
await request.Db.Todos.FindAsync(request.Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
다음 코드는 앱의 추가 엔드포인트를 보여 줍니다.
app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
var todoItem = new Todo
{
IsComplete = Dto.IsComplete,
Name = Dto.Name
};
Db.Todos.Add(todoItem);
await Db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
var todo = await Db.Todos.FindAsync(Id);
if (todo is null) return Results.NotFound();
todo.Name = Dto.Name;
todo.IsComplete = Dto.IsComplete;
await Db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
if (await Db.Todos.FindAsync(Id) is Todo todo)
{
Db.Todos.Remove(todo);
await Db.SaveChangesAsync();
return Results.Ok(new TodoItemDTO(todo));
}
return Results.NotFound();
});
다음 클래스는 매개 변수 목록을 리팩터링하는 데 사용합니다.
class CreateTodoItemRequest
{
public TodoItemDTO Dto { get; set; } = default!;
public TodoDb Db { get; set; } = default!;
}
class EditTodoItemRequest
{
public int Id { get; set; }
public TodoItemDTO Dto { get; set; } = default!;
public TodoDb Db { get; set; } = default!;
}
다음 코드는 AsParameters를 사용하는 리팩터링된 엔드포인트와 위의 struct 및 클래스를 보여 줍니다.
app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
var todoItem = new Todo
{
IsComplete = request.Dto.IsComplete,
Name = request.Dto.Name
};
request.Db.Todos.Add(todoItem);
await request.Db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
var todo = await request.Db.Todos.FindAsync(request.Id);
if (todo is null) return Results.NotFound();
todo.Name = request.Dto.Name;
todo.IsComplete = request.Dto.IsComplete;
await request.Db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
{
request.Db.Todos.Remove(todo);
await request.Db.SaveChangesAsync();
return Results.Ok(new TodoItemDTO(todo));
}
return Results.NotFound();
});
다음 record 형식을 사용하면 위의 매개 변수를 바꿀 수 있습니다.
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
struct를 AsParameters와 함께 사용하면 record 유형보다 성능이 개선될 수 있습니다.
완전한 샘플 코드는 AspNetCore.Docs.Samples 리포지토리에 있습니다.
사용자 지정 바인딩
매개 변수 바인딩을 사용자 지정하는 세 가지 방법이 있습니다.
- 경로, 쿼리, 헤더 바인딩 소스의 경우 형식의 정적
TryParse메서드를 추가하여 사용자 지정 형식을 바인딩합니다. - 형식에서
BindAsync메서드를 구현하여 바인딩 프로세스를 제어합니다. - 고급 시나리오의 경우 IBindableFromHttpContext<TSelf>에서 바로 사용자 지정 바인딩 논리를 제공할 수 있도록
HttpContext인터페이스를 구현합니다.
TryParse
TryParse에는 다음 두 가지 API가 있습니다.
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
다음 코드는 URI가 Point: 12.3, 10.1인 /map?Point=12.3,10.1을 표시합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");
app.Run();
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(string? value, IFormatProvider? provider,
out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
BindAsync
BindAsync에는 다음 API가 있습니다.
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
다음 코드는 URI가 SortBy:xyz, SortDirection:Desc, CurrentPage:99인 /products?SortBy=xyz&SortDir=Desc&Page=99을 표시합니다.
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
$"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");
app.Run();
public class PagingData
{
public string? SortBy { get; init; }
public SortDirection SortDirection { get; init; }
public int CurrentPage { get; init; } = 1;
public static ValueTask<PagingData?> BindAsync(HttpContext context,
ParameterInfo parameter)
{
const string sortByKey = "sortBy";
const string sortDirectionKey = "sortDir";
const string currentPageKey = "page";
Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
ignoreCase: true, out var sortDirection);
int.TryParse(context.Request.Query[currentPageKey], out var page);
page = page == 0 ? 1 : page;
var result = new PagingData
{
SortBy = context.Request.Query[sortByKey],
SortDirection = sortDirection,
CurrentPage = page
};
return ValueTask.FromResult<PagingData?>(result);
}
}
public enum SortDirection
{
Default,
Asc,
Desc
}
사용자 지정 매개변수 바인딩을 IBindableFromHttpContext 사용하여
ASP.NET Core는 최소 API에서 사용자 지정 매개 변수 바인딩을 지원하기 위해 IBindableFromHttpContext<TSelf> 인터페이스를 제공합니다. C# 11의 정적 추상 멤버를 사용하여 도입된 이 인터페이스를 사용하면 경로 처리기 매개 변수에서 직접 HTTP 컨텍스트에서 바인딩할 수 있는 형식을 만들 수 있습니다.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
인터페이스를 IBindableFromHttpContext<TSelf> 구현하여 HttpContext에서 자체 바인딩 논리를 처리하는 사용자 지정 형식을 만들 수 있습니다. 경로 처리기에 이 형식의 매개 변수가 포함된 경우 프레임워크는 자동으로 정적 BindAsync 메서드를 호출하여 인스턴스를 만듭니다.
using CustomBindingExample;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/", () => "Hello, IBindableFromHttpContext example!");
app.MapGet("/custom-binding", (CustomBoundParameter param) =>
{
return $"Value from custom binding: {param.Value}";
});
app.MapGet("/combined/{id}", (int id, CustomBoundParameter param) =>
{
return $"ID: {id}, Custom Value: {param.Value}";
});
다음은 HTTP 헤더에서 바인딩하는 사용자 지정 매개 변수의 예제 구현입니다.
using System.Reflection;
namespace CustomBindingExample;
public class CustomBoundParameter : IBindableFromHttpContext<CustomBoundParameter>
{
public string Value { get; init; } = default!;
public static ValueTask<CustomBoundParameter?> BindAsync(HttpContext context, ParameterInfo parameter)
{
// Custom binding logic here
// This example reads from a custom header
var value = context.Request.Headers["X-Custom-Header"].ToString();
// If no header was provided, you could fall back to a query parameter
if (string.IsNullOrEmpty(value))
{
value = context.Request.Query["customValue"].ToString();
}
return ValueTask.FromResult<CustomBoundParameter?>(new CustomBoundParameter
{
Value = value
});
}
}
사용자 지정 바인딩 논리 내에서 유효성 검사를 구현할 수도 있습니다.
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
바인딩 실패
바인딩이 실패할 경우 프레임워크는 디버그 메시지를 로그하고 실패 모드에 따라 다양한 상태 코드를 클라이언트에 반환합니다.
| 실패 모드 | null 허용 매개 변수 형식 | 바인딩 원본 | 상태 코드 |
|---|---|---|---|
{ParameterType}.TryParse
false를 반환합니다 |
yes | route/query/header | 400 |
{ParameterType}.BindAsync
null를 반환합니다 |
yes | custom | 400 |
{ParameterType}.BindAsync가 throw |
중요하지 않음 | custom | 500 |
| JSON 본문을 역직렬화하지 못함 | 중요하지 않음 | body | 400 |
잘못된 콘텐츠 형식(application/json이 아님) |
중요하지 않음 | body | 415 |
바인딩 우선 순위
매개 변수에서 바인딩 소스를 결정하는 규칙:
- 매개 변수(From* 특성)에 다음 순서로 정의된 명시적 특성:
- 경로 값:
[FromRoute] - 쿼리 문자열:
[FromQuery] - 헤더:
[FromHeader] - 본문:
[FromBody] - 서비스:
[FromServices] - 매개 변수 값:
[AsParameters]
- 경로 값:
- 특수 형식
HttpContext-
HttpRequest(HttpContext.Request) -
HttpResponse(HttpContext.Response) -
ClaimsPrincipal(HttpContext.User) -
CancellationToken(HttpContext.RequestAborted) -
IFormFileCollection(HttpContext.Request.Form.Files) -
IFormFile(HttpContext.Request.Form.Files[paramName]) -
Stream(HttpContext.Request.Body) -
PipeReader(HttpContext.Request.BodyReader)
- 매개 변수 형식에 유효한 정적
BindAsync메서드가 있습니다. - 매개 변수 형식이 문자열이거나 매개 변수 형식에 유효한 정적
TryParse메서드가 있습니다.- 경로 템플릿에 매개 변수 이름이 있는 경우 에서
app.Map("/todo/{id}", (int id) => {});경로id에서 바인딩됩니다. - 쿼리 문자열에서 바인딩됩니다.
- 경로 템플릿에 매개 변수 이름이 있는 경우 에서
- 매개 변수 형식이 종속성 주입에서 제공되는 서비스인 경우 해당 서비스를 원본으로 사용합니다.
- 매개 변수가 본문의 매개 변수입니다.
본문 바인딩에 대한 JSON 역직렬화 옵션 구성
본문 바인딩 소스는 역직렬화에 사용합니다 System.Text.Json . 이 기본값은 변경할 수 없지만 JSON 직렬화 및 역직렬화 옵션을 구성할 수 있습니다.
전역적으로 JSON 역직렬화 옵션 구성
앱에 대해 전역적으로 적용되는 옵션은 호출하여 ConfigureHttpJsonOptions구성할 수 있습니다. 다음 예제에서는 공용 필드를 포함하고 JSON 출력 형식을 지정합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/", (Todo todo) => {
if (todo is not null) {
todo.Name = todo.NameField;
}
return todo;
});
app.Run();
class Todo {
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "nameField":"Walk dog",
// "isComplete":false
// }
샘플 코드는 serialization 및 deserialization을 모두 구성하므로 출력 JSON에서 읽고 NameField 포함 NameField 할 수 있습니다.
엔드포인트에 대한 JSON 역직렬화 옵션 구성
ReadFromJsonAsync 에는 개체를 허용하는 오버로드가 있습니다 JsonSerializerOptions . 다음 예제에서는 공용 필드를 포함하고 JSON 출력 형식을 지정합니다.
using System.Text.Json;
var app = WebApplication.Create();
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
IncludeFields = true,
WriteIndented = true
};
app.MapPost("/", async (HttpContext context) => {
if (context.Request.HasJsonContentType()) {
var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
if (todo is not null) {
todo.Name = todo.NameField;
}
return Results.Ok(todo);
}
else {
return Results.BadRequest();
}
});
app.Run();
class Todo
{
public string? Name { get; set; }
public string? NameField;
public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
// "name":"Walk dog",
// "isComplete":false
// }
앞의 코드는 역직렬화에만 사용자 지정된 옵션을 적용하므로 출력 JSON은 제외됩니다 NameField.
요청 본문 읽기
HttpContext 또는 HttpRequest 매개 변수를 사용하여 직접 요청 본문을 읽습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());
await using var writeStream = File.Create(filePath);
await request.BodyReader.CopyToAsync(writeStream);
});
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
- HttpRequest.BodyReader를 사용하여 요청 본문에 액세스합니다.
- 요청 본문을 로컬 파일에 복사합니다.
Responses
경로 처리기는 다음과 같은 형식의 반환 값을 지원합니다.
-
IResult기반 - 여기에는Task<IResult>및ValueTask<IResult>가 포함됩니다. -
string- 여기에는Task<string>및ValueTask<string>이 포함됩니다. -
T(다른 모든 형식) - 여기에는Task<T>및ValueTask<T>가 포함됩니다.
| 반환 값 | Behavior | Content-Type |
|---|---|---|
IResult |
프레임워크가 IResult.ExecuteAsync를 호출합니다. |
IResult 구현에 의해 결정됩니다. |
string |
프레임워크가 응답에 직접 문자열을 씁니다. | text/plain |
T(기타 형식) |
프레임워크 JSON은 응답을 직렬화합니다. | application/json |
처리기 반환 값을 라우팅하는 자세한 가이드는 최소 API 애플리케이션에서 응답 만들기를 참조하세요.
예시 반환 값
문자열 반환 값
app.MapGet("/hello", () => "Hello World");
JSON 반환 값
app.MapGet("/hello", () => new { Message = "Hello World" });
TypedResults 반환
다음 코드는 다음을 반환합니다.TypedResults
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
IResult 반환 값
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
다음 예는 기본 제공 결과 형식을 사용하여 응답을 사용자 지정합니다.
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
사용자 지정 상태 코드
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
기본 제공 결과
일반적인 결과 도우미는 정적 클래스 및 Results 정적 클래스에 TypedResults 있습니다. 반환은 TypedResults 반환하는 Results것이 좋습니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
결과 사용자 지정
애플리케이션은 사용자 지정 IResult 형식을 구현하여 응답을 제어할 수 있습니다. 다음 코드는 HTML 결과 형식의 예입니다.
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
이러한 사용자 지정 결과를 더 검색 가능하게 만들려면 Microsoft.AspNetCore.Http.IResultExtensions에 확장 메서드를 추가하는 것이 좋습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
입력된 결과
인터페이스는 IResult 반환된 개체를 HTTP 응답으로 직렬화하는 JSON에 대한 암시적 지원을 활용하지 않는 최소 API에서 반환된 값을 나타낼 수 있습니다. 정적 Results 클래스는 다양한 형식의 응답을 나타내는 다양한 IResult 개체를 만드는 데 사용됩니다. 예를 들어 응답 상태 코드를 설정하거나 다른 URL로 리디렉션합니다.
IResult를 구현하는 형식은 공용이므로 테스트할 때 형식 어설션을 허용합니다. 다음은 그 예입니다.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
정적 TypedResults 클래스에서 해당 메서드의 반환 형식을 확인하여 캐스팅할 올바른 퍼블릭 IResult 형식을 찾을 수 있습니다.
자세한 예제는 최소 API 애플리케이션에서 응답 만들기 를 참조하세요.
Filters
최소 API 앱의 필터를 참조하세요.
Authorization
권한 부여 정책을 사용하여 경로를 보호할 수 있습니다. 권한 부여 정책은 [Authorize] 특성을 통해 또는 RequireAuthorization 메서드를 사용하여 선언할 수 있습니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
이전 코드는 RequireAuthorization을 사용하여 작성할 수 있습니다.
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
다음 샘플은 정책 기반 권한 부여를 사용합니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/admin", [Authorize("AdminsOnly")] () =>
"The /admin endpoint is for admins only.");
app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
.RequireAuthorization("AdminsOnly");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용
[AllowAnonymous]는 인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용합니다.
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
경로는 CORS 정책을 사용하여 CORS를 사용할 수 있습니다. CORS는 [EnableCors] 특성을 통해 또는 RequireCors 메서드를 사용하여 선언할 수 있습니다. 다음 샘플은 CORS를 사용하도록 설정합니다.
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
자세한 내용은 ASP.NET Core에서 CORS(원본 간 요청) 사용을 참조하세요.
참고하십시오
이 문서:
- 최소 API에 대한 빠른 참조를 제공합니다.
- 숙련된 개발자를 대상으로 합니다. 소개는 자습서: ASP.NET Core를 사용하여 최소 API 만들기를 참조하세요.
최소 API는 다음으로 구성됩니다.
- WebApplication 및 WebApplicationBuilder
- 경로 처리기
WebApplication
다음 코드는 ASP.NET Core 템플릿에서 생성됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
위의 코드는 명령줄에서 dotnet new web을 통해 만들거나 Visual Studio에서 빈 웹 템플릿을 선택하여 만들 수 있습니다.
다음 코드는 WebApplication를 명시적으로 만들지 않고 app(WebApplicationBuilder)을 만듭니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create는 미리 구성된 기본값을 사용하여 WebApplication 클래스의 새 인스턴스를 초기화합니다.
포트 작업
Visual Studio 또는 dotnet new를 사용하여 웹앱을 만들면 앱이 응답하는 포트를 지정하는 Properties/launchSettings.json 파일이 만들어집니다. 다음에 오는 포트 설정 샘플에서는 Visual Studio에서 앱을 실행하면 Unable to connect to web server 'AppName' 오류 대화 상자가 반환됩니다. 명령줄에서 다음 포트 변경 샘플을 실행합니다.
다음 섹션에서는 앱이 응답하는 포트를 설정합니다.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
위의 코드에서 앱은 포트 3000에 응답합니다.
여러 포트
위의 코드에서 앱은 포트 3000 및 4000에 응답합니다.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
명령줄에서 포트 설정
다음 명령은 앱이 포트 7777에 응답하도록 합니다.
dotnet run --urls="https://localhost:7777"
Kestrel 엔드포인트가 appsettings.json 파일에도 구성된 경우 appsettings.json 파일에서 지정된 URL이 사용됩니다. 자세한 내용은 Kestrel 엔드포인트 구성을 참조하세요.
환경에서 포트 읽기
다음 코드는 환경에서 포트를 읽습니다.
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
환경에서 포트를 설정하는 기본 방법은 다음 섹션에 나오는 ASPNETCORE_URLS 환경 변수를 사용하는 것입니다.
ASPNETCORE_URLS 환경 변수를 통해 포트 설정
ASPNETCORE_URLS 환경 변수를 사용하여 포트를 설정할 수 있습니다.
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS는 여러 URL을 지원합니다.
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
모든 인터페이스에서 수신 대기
다음 샘플은 모든 인터페이스에서의 수신 대기를 보여 줍니다.
http://*:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://+:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://0.0.0.0:3000
var app = WebApplication.Create(args);
app.Urls.Add("http://0.0.0.0:3000");
app.MapGet("/", () => "Hello World");
app.Run();
ASPNETCORE_URLS를 사용하여 모든 인터페이스에서 수신 대기
이전 샘플은 ASPNETCORE_URLS를 사용할 수 있습니다.
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
개발 인증서로 HTTPS 지정
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
개발 인증서에 대한 자세한 내용은 Windows 및 macOS에서 ASP.NET Core HTTPS 개발 인증서 신뢰를 참조하세요.
사용자 지정 인증서를 사용하여 HTTPS 지정
다음 섹션에서는 파일 및 구성을 통해 사용자 지정 인증서를 지정하는 appsettings.json 방법을 보여 있습니다.
appsettings.json을 사용하여 사용자 지정 인증서 지정
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
구성을 통해 사용자 지정 인증서 지정
var builder = WebApplication.CreateBuilder(args);
// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
인증서 API 사용
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");
httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});
var app = builder.Build();
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
환경 읽기
var app = WebApplication.Create(args);
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}
app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");
app.Run();
환경을 사용하는 방법에 대한 자세한 내용은 ASP.NET Core 런타임 환경을 참조하세요.
Configuration
다음 코드는 구성 시스템에서 읽습니다.
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Hello";
app.MapGet("/", () => message);
app.Run();
자세한 내용은 ASP.NET Core의 구성을 참조하세요.
Logging
다음 코드는 로그온 애플리케이션 시작에 메시지를 씁니다.
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
자세한 내용은 .NET 및 ASP.NET Core의 로깅을 참조하세요.
DI(종속성 주입) 컨테이너 액세스
다음 코드에서는 애플리케이션을 시작하는 동안 DI 컨테이너에서 서비스를 가져오는 방법을 보여줍니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();
var app = builder.Build();
app.MapControllers();
using (var scope = app.Services.CreateScope())
{
var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}
app.Run();
자세한 내용은 ASP.NET Core에서 종속성 주입을 참조하세요.
WebApplicationBuilder
이 섹션에는 WebApplicationBuilder를 사용하는 샘플 코드가 포함되어 있습니다.
콘텐츠 루트, 애플리케이션 이름, 환경 변경
다음 코드는 콘텐츠 루트, 애플리케이션 이름, 환경을 설정합니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다.
자세한 내용은 ASP.NET Core 기본 사항 개요를 참조하세요
환경 변수 또는 명령줄을 통해 콘텐츠 루트, 앱 이름, 환경 변경
다음 표는 콘텐츠 루트, 앱 이름, 환경 변경에 사용되는 환경 변수와 명령줄 인수를 보여 줍니다.
| feature | 환경 변수 | 명령줄 인수 |
|---|---|---|
| 애플리케이션 이름 | ASPNETCORE_APPLICATIONNAME | --applicationName |
| 환경 이름 | ASPNETCORE_ENVIRONMENT | --environment |
| 콘텐츠 루트 | ASPNETCORE_CONTENTROOT | --contentRoot |
구성 공급자 추가
다음 샘플은 INI 구성 공급자를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
자세한 내용은 ASP.NET Core의 구성에서 파일 구성 공급자를 참조하세요.
구성 읽기
기본적으로 WebApplicationBuilder는 다음을 포함하여 여러 원본에서 구성을 읽습니다.
-
appSettings.json및appSettings.{environment}.json - 환경 변수
- 명령줄
구성 소스 읽기의 전체 목록은 ASP.NET Core의 구성에서 기본 구성을 참조하세요.
다음 코드는 구성에서 HelloKey를 읽고 / 엔드포인트에 값을 표시합니다. 구성 값이 null이면 "Hello"가 message에 할당됩니다.
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
환경 읽기
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
로깅 공급자 추가
var builder = WebApplication.CreateBuilder(args);
// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();
var app = builder.Build();
app.MapGet("/", () => "Hello JSON console!");
app.Run();
서비스 추가
var builder = WebApplication.CreateBuilder(args);
// Add the memory cache services.
builder.Services.AddMemoryCache();
// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();
IHostBuilder 사용자 지정
IHostBuilder의 기존 확장 메서드는 호스트 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
IWebHostBuilder 사용자 지정
IWebHostBuilder의 확장 메서드는 WebApplicationBuilder.WebHost 속성을 사용하여 액세스할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();
var app = builder.Build();
app.MapGet("/", () => "Hello HTTP.sys");
app.Run();
웹 루트 변경
기본적으로 웹 루트는 wwwroot 폴더의 콘텐츠 루트를 기준으로 합니다. 웹 루트는 정적 파일 미들웨어가 정적 파일을 찾는 위치입니다. 웹 루트는 WebHostOptions, 명령줄 또는 UseWebRoot 메서드를 사용하여 변경할 수 있습니다.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
사용자 지정 DI(종속성 주입) 컨테이너
다음 예제에서는 Autofac을 사용합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
var app = builder.Build();
미들웨어 추가
기존 ASP.NET Core 미들웨어는 WebApplication에서 구성할 수 있습니다.
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.
개발자 예외 페이지
WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 WebApplicationBuilder 클래스의 새 인스턴스를 초기화합니다. 개발자 예외 페이지는 미리 구성된 기본값에서 사용하도록 설정됩니다.
개발 환경에서 다음 코드가 실행되는 경우 /로 이동하면 예외를 보여 주는 친숙한 페이지가 렌더링됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ASP.NET Core 미들웨어 기본 사항
다음 표에는 최소 API와 함께 자주 사용되는 미들웨어 일부가 나와 있습니다.
| Middleware | Description | API |
|---|---|---|
| Authentication | 인증 지원을 제공합니다. | UseAuthentication |
| Authorization | 권한 부여 지원을 제공합니다. | UseAuthorization |
| CORS | 원본 간 리소스 공유를 구성합니다. | UseCors |
| 예외 처리기 | 미들웨어 파이프라인이 throw하는 예외를 전역으로 처리합니다. | UseExceptionHandler |
| 전달된 헤더 | 프록시된 헤더를 현재 요청에 전달합니다. | UseForwardedHeaders |
| HTTPS 리디렉션 | 모든 HTTP 요청을 HTTPS로 리디렉션합니다. | UseHttpsRedirection |
| HSTS(HTTP 엄격한 전송 보안) | 특별한 응답 헤더를 추가하는 보안 향상 미들웨어입니다. | UseHsts |
| 요청 로깅 | HTTP 요청 및 응답 로깅을 지원합니다. | UseHttpLogging |
| W3C 요청 로깅 | W3C 형식의 HTTP 요청 및 응답 로깅을 지원합니다. | UseW3CLogging |
| 응답 캐싱 | 응답 캐시에 대한 지원을 제공합니다. | UseResponseCaching |
| 응답 압축 | 응답 압축에 대한 지원을 제공합니다. | UseResponseCompression |
| Session | 사용자 세션 관리에 대한 지원을 제공합니다. | UseSession |
| 정적 파일 | 정적 파일 및 디렉터리 검색 처리에 대한 지원을 제공합니다. | UseStaticFiles, UseFileServer |
| WebSockets | WebSocket 프로토콜을 활성화합니다. | UseWebSockets |
요청 처리
다음 섹션에서는 라우팅, 매개 변수 바인딩, 응답을 다룹니다.
Routing
구성된 WebApplication은 Map{Verb} 및 MapMethods를 지원합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");
app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" },
() => "This is an options or head request ");
app.Run();
경로 처리기
경로 처리기는 경로가 일치할 때 실행되는 메서드입니다. 경로 처리기는 동기 또는 비동기를 포함한 모든 형태의 함수일 수 있습니다. 경로 처리기는 람다 식, 로컬 함수, 인스턴스 메서드 또는 정적 메서드일 수 있습니다.
람다 식
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/inline", () => "This is an inline lambda");
var handler = () => "This is a lambda variable";
app.MapGet("/", handler);
app.Run();
로컬 함수
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
인스턴스 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new HelloHandler();
app.MapGet("/", handler.Hello);
app.Run();
class HelloHandler
{
public string Hello()
{
return "Hello Instance method";
}
}
정적 메서드
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", HelloHandler.Hello);
app.Run();
class HelloHandler
{
public static string Hello()
{
return "Hello static method";
}
}
명명된 엔드포인트 및 링크 생성
경로의 URL을 생성하기 위해 경로에 이름을 지정할 수 있습니다. 이름이 지정된 경로를 사용하면 앱에서 경로를 하드 코드로 작성할 필요가 없습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello named route")
.WithName("hi");
app.MapGet("/", (LinkGenerator linker) =>
$"The link to the hello route is {linker.GetPathByName("hi", values: null)}");
app.Run();
이전 코드는 The link to the hello endpoint is /hello 엔드포인트에서 /를 표시합니다.
참고: 엔드포인트 이름은 대/소문자를 구분합니다.
엔드포인트 이름:
- 전역적으로 고유해야 합니다.
- OpenAPI 지원을 사용할 때 OpenAPI 작업 ID로 사용됩니다. 자세한 내용은 OpenAPI를 참조하세요.
경로 매개 변수
경로 패턴 정의의 일부로 경로 매개 변수를 캡처할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{userId}/books/{bookId}",
(int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");
app.Run();
이전 코드는 The user id is 3 and book id is 7 URI에서 /users/3/books/7을 반환합니다.
경로 처리기는 캡처할 매개 변수를 선언할 수 있습니다. 캡처하도록 선언된 매개 변수와 함께 경로에 요청이 수행되면 매개 변수가 구문 분석되어 처리기에 전달됩니다. 이렇게 하면 형식이 안전한 방식으로 값을 쉽게 캡처할 수 있습니다. 이전 코드에서 userId와 bookId는 모두 int입니다.
이전 코드에서 경로 값 중 하나를 int로 변환할 수 없는 경우 예외가 throw됩니다. GET 요청 /users/hello/books/3는 다음 예외를 throw합니다.
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
와일드카드 및 catch all 경로
다음 catch all 경로는 `/posts/hello` 엔드포인트에서 Routing to hello를 반환합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
경로 제약 조건
경로 제약 조건은 경로의 일치 동작을 제한합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text)));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");
app.Run();
다음 표는 이전 경로 템플릿과 동작을 보여 줍니다.
| 경로 템플릿 | URI 일치 예제 |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
자세한 내용은 ASP.NET Core의 라우팅에서 경로 제약 조건 참조를 참조하세요.
매개 변수 바인딩
매개 변수 바인딩은 경로 처리기가 표현하는 강력한 형식의 매개 변수로 요청 데이터를 변환하는 프로세스입니다. 바인딩 소스는 매개 변수가 바인딩되는 위치를 결정합니다. 바인딩 소스는 HTTP 메서드 및 매개 변수 형식에 따라 명시적이거나 유추될 수 있습니다.
지원되는 바인딩 소스:
- 경로 값
- 쿼리 문자열
- Header
- 본문(JSON 형식)
- 종속성 주입에서 제공하는 서비스
- Custom
Note
양식 값에서 바인딩은 .NET에서 기본적으로 지원되지 않습니다.
다음 예제 GET 경로 처리기는 이러한 매개 변수 바인딩 소스 중 일부를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", (int id,
int page,
[FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
Service service) => { });
class Service { }
다음 표에서는 이전 예제에서 사용된 매개 변수와 연결된 바인딩 소스 간의 관계를 보여 줍니다.
| Parameter | 바인딩 원본 |
|---|---|
id |
경로 값 |
page |
쿼리 문자열 |
customHeader |
header |
service |
종속성 주입에서 제공 |
HTTP 메서드 GET, HEAD, OPTIONS, DELETE는 본문에서 암시적으로 바인딩되지 않습니다. 이러한 HTTP 메서드에 대해 본문(JSON 형식)에서 바인딩하려면 로 [FromBody]하거나 HttpRequest에서 읽습니다.
다음 예제 POST 경로 처리기는 person 매개 변수에 대해 본문(JSON 형식)의 바인딩 소스를 사용합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
이전 예제의 매개 변수는 모두 요청 데이터에서 자동으로 바인딩됩니다. 매개 변수 바인딩이 제공하는 편의를 설명하기 위해 다음 예제 경로 처리기는 요청에서 직접 요청 데이터를 읽는 방법을 보여 줍니다.
app.MapGet("/{id}", (HttpRequest request) =>
{
var id = request.RouteValues["id"];
var page = request.Query["page"];
var customHeader = request.Headers["X-CUSTOM-HEADER"];
// ...
});
app.MapPost("/", async (HttpRequest request) =>
{
var person = await request.ReadFromJsonAsync<Person>();
// ...
});
명시적 매개 변수 바인딩
특성을 사용하여 매개 변수가 바인딩되는 위치를 명시적으로 선언할 수 있습니다.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
// Added as service
builder.Services.AddSingleton<Service>();
var app = builder.Build();
app.MapGet("/{id}", ([FromRoute] int id,
[FromQuery(Name = "p")] int page,
[FromServices] Service service,
[FromHeader(Name = "Content-Type")] string contentType)
=> {});
class Service { }
record Person(string Name, int Age);
| Parameter | 바인딩 원본 |
|---|---|
id |
이름이 id인 경로 값 |
page |
이름이 "p"인 쿼리 문자열 |
service |
종속성 주입에서 제공 |
contentType |
이름이 "Content-Type"인 헤더 |
Note
양식 값에서 바인딩은 .NET에서 기본적으로 지원되지 않습니다.
DI를 사용한 매개 변수 바인딩
최소 API에 대한 매개 변수 바인딩은 형식이 서비스로 구성된 경우 종속성 주입을 통해 매개 변수를 바인딩합니다. 매개 변수에 [FromServices] 특성을 명시적으로 적용할 필요는 없습니다. 다음 코드에서는 두 작업 모두 시간을 반환합니다.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
var app = builder.Build();
app.MapGet("/", ( IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();
선택적 매개 변수
경로 처리기에서 선언된 매개 변수는 필수로 처리됩니다.
- 요청이 경로와 일치하는 경우 요청에 모든 필수 매개 변수가 제공되는 경우에만 경로 처리기가 실행됩니다.
- 모든 필수 매개 변수를 제공하지 못하면 오류가 발생합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");
app.Run();
| URI | result |
|---|---|
/products?pageNumber=3 |
반환된 3개 |
/products |
BadHttpRequestException: 필수 매개 변수 "int pageNumber"가 쿼리 문자열에서 제공되지 않았습니다. |
/products/1 |
HTTP 404 오류, 일치하는 경로 없음 |
pageNumber를 선택 사항으로 지정하려면 형식을 선택 사항으로 정의하거나 기본값을 제공합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";
app.MapGet("/products2", ListProducts);
app.Run();
| URI | result |
|---|---|
/products?pageNumber=3 |
반환된 3개 |
/products |
반환된 1 |
/products2 |
반환된 1 |
이전 null 허용 값과 기본값은 모든 원본에 적용됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
이전 코드는 요청 본문을 보내지 않은 경우 null 곱을 사용하여 메서드를 호출합니다.
참고: 잘못된 데이터를 제공하고 매개 변수가 null을 허용하면 경로 처리기가 실행되지 않습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");
app.Run();
| URI | result |
|---|---|
/products?pageNumber=3 |
3 반환됨 |
/products |
1 반환됨 |
/products?pageNumber=two |
BadHttpRequestException: "2"에서 "Nullable<int> pageNumber" 매개 변수를 바인딩하지 못했습니다. |
/products/two |
HTTP 404 오류, 일치하는 경로 없음 |
자세한 내용은 바인딩 실패 섹션을 참조하세요.
특수 형식
다음 형식은 명시적 특성 없이 바인딩됩니다.
HttpContext: 현재 HTTP 요청 또는 응답에 대한 모든 정보를 포함하는 컨텍스트입니다.
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest 및 HttpResponse: HTTP 요청 및 HTTP 응답:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken: 현재 HTTP 요청에 연결된 취소 토큰:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal: HttpContext.User에서 바인딩된 요청에 연결된 사용자:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
사용자 지정 바인딩
매개 변수 바인딩을 사용자 지정하는 방법에는 다음 두 가지가 있습니다.
- 경로, 쿼리, 헤더 바인딩 소스의 경우 형식의 정적
TryParse메서드를 추가하여 사용자 지정 형식을 바인딩합니다. - 형식에서
BindAsync메서드를 구현하여 바인딩 프로세스를 제어합니다.
TryParse
TryParse에는 다음 두 가지 API가 있습니다.
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
다음 코드는 URI가 Point: 12.3, 10.1인 /map?Point=12.3,10.1을 표시합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");
app.Run();
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(string? value, IFormatProvider? provider,
out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
BindAsync
BindAsync에는 다음 API가 있습니다.
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
다음 코드는 URI가 SortBy:xyz, SortDirection:Desc, CurrentPage:99인 /products?SortBy=xyz&SortDir=Desc&Page=99을 표시합니다.
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
$"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");
app.Run();
public class PagingData
{
public string? SortBy { get; init; }
public SortDirection SortDirection { get; init; }
public int CurrentPage { get; init; } = 1;
public static ValueTask<PagingData?> BindAsync(HttpContext context,
ParameterInfo parameter)
{
const string sortByKey = "sortBy";
const string sortDirectionKey = "sortDir";
const string currentPageKey = "page";
Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
ignoreCase: true, out var sortDirection);
int.TryParse(context.Request.Query[currentPageKey], out var page);
page = page == 0 ? 1 : page;
var result = new PagingData
{
SortBy = context.Request.Query[sortByKey],
SortDirection = sortDirection,
CurrentPage = page
};
return ValueTask.FromResult<PagingData?>(result);
}
}
public enum SortDirection
{
Default,
Asc,
Desc
}
바인딩 실패
바인딩이 실패할 경우 프레임워크는 디버그 메시지를 로그하고 실패 모드에 따라 다양한 상태 코드를 클라이언트에 반환합니다.
| 실패 모드 | null 허용 매개 변수 형식 | 바인딩 원본 | 상태 코드 |
|---|---|---|---|
{ParameterType}.TryParse
false를 반환합니다 |
yes | route/query/header | 400 |
{ParameterType}.BindAsync
null를 반환합니다 |
yes | custom | 400 |
{ParameterType}.BindAsync가 throw |
중요하지 않음 | custom | 500 |
| JSON 본문을 역직렬화하지 못함 | 중요하지 않음 | body | 400 |
잘못된 콘텐츠 형식(application/json이 아님) |
중요하지 않음 | body | 415 |
바인딩 우선 순위
매개 변수에서 바인딩 소스를 결정하는 규칙:
- 매개 변수(From* 특성)에 다음 순서로 정의된 명시적 특성:
- 경로 값:
[FromRoute] - 쿼리 문자열:
[FromQuery] - 헤더:
[FromHeader] - 본문:
[FromBody] - 서비스:
[FromServices]
- 경로 값:
- 특수 형식
- 매개 변수 형식에 유효한
BindAsync메서드가 있습니다. - 매개 변수 형식이 문자열이거나 매개 변수 형식에 유효한
TryParse메서드가 있습니다.- 경로 템플릿에 매개 변수 이름이 있는 경우 에서
app.Map("/todo/{id}", (int id) => {});경로id에서 바인딩됩니다. - 쿼리 문자열에서 바인딩됩니다.
- 경로 템플릿에 매개 변수 이름이 있는 경우 에서
- 매개 변수 형식이 종속성 주입에서 제공되는 서비스인 경우 해당 서비스를 원본으로 사용합니다.
- 매개 변수가 본문의 매개 변수입니다.
JSON 바인딩 사용자 지정
본문 바인딩 소스는 역직렬화에 System.Text.Json을 사용합니다. 이 기본값은 변경할 수 없지만 앞서 설명한 다른 방법을 사용하여 사용자 지정할 수 있습니다. JSON 직렬 변환기 옵션을 사용자 지정하려면 다음과 유사한 코드를 사용하세요.
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
// Configure JSON options.
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.IncludeFields = true;
});
var app = builder.Build();
app.MapPost("/products", (Product product) => product);
app.Run();
class Product
{
// These are public fields, not properties.
public int Id;
public string? Name;
}
앞의 코드가 하는 역할은 다음과 같습니다.
- 입력 및 출력 기본 JSON 옵션을 모두 구성합니다.
- 다음 JSON을 반환합니다.
게시할 때{ "id": 1, "name": "Joe Smith" }{ "Id": 1, "Name": "Joe Smith" }
요청 본문 읽기
HttpContext 또는 HttpRequest 매개 변수를 사용하여 직접 요청 본문을 읽습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());
await using var writeStream = File.Create(filePath);
await request.BodyReader.CopyToAsync(writeStream);
});
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
- HttpRequest.BodyReader를 사용하여 요청 본문에 액세스합니다.
- 요청 본문을 로컬 파일에 복사합니다.
Responses
경로 처리기는 다음과 같은 형식의 반환 값을 지원합니다.
-
IResult기반 - 여기에는Task<IResult>및ValueTask<IResult>가 포함됩니다. -
string- 여기에는Task<string>및ValueTask<string>이 포함됩니다. -
T(다른 모든 형식) - 여기에는Task<T>및ValueTask<T>가 포함됩니다.
| 반환 값 | Behavior | Content-Type |
|---|---|---|
IResult |
프레임워크가 IResult.ExecuteAsync를 호출합니다. |
IResult 구현에 의해 결정됩니다. |
string |
프레임워크가 응답에 직접 문자열을 씁니다. | text/plain |
T(기타 형식) |
프레임워크가 응답을 JSON 직렬화합니다. | application/json |
예시 반환 값
문자열 반환 값
app.MapGet("/hello", () => "Hello World");
JSON 반환 값
app.MapGet("/hello", () => new { Message = "Hello World" });
IResult 반환 값
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
다음 예는 기본 제공 결과 형식을 사용하여 응답을 사용자 지정합니다.
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
사용자 지정 상태 코드
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () =>
{
var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
// Proxy the response as JSON
return Results.Stream(stream, "application/json");
});
app.Run();
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
기본 제공 결과
Microsoft.AspNetCore.Http.Results 정적 클래스에는 일반적인 결과 도우미가 있습니다.
| Description | 응답 유형 | 상태 코드 | API |
|---|---|---|---|
| 고급 옵션을 사용하여 JSON 응답 쓰기 | application/json | 200 | Results.Json |
| JSON 응답 쓰기 | application/json | 200 | Results.Ok |
| 텍스트 응답 쓰기 | text/plain(기본값), 구성 가능 | 200 | Results.Text |
| 응답을 바이트로 쓰기 | application/octet-stream(기본값), 구성 가능 | 200 | Results.Bytes |
| 응답에 바이트 스트림 쓰기 | application/octet-stream(기본값), 구성 가능 | 200 | Results.Stream |
| 콘텐츠 처리 헤더로 다운로드하기 위해 파일을 응답으로 스트리밍 | application/octet-stream(기본값), 구성 가능 | 200 | Results.File |
| 선택적 JSON 응답을 사용하여 상태 코드를 404로 설정 | N/A | 404 | Results.NotFound |
| 상태 코드를 204로 설정 | N/A | 204 | Results.NoContent |
| 선택적 JSON 응답을 사용하여 상태 코드를 422로 설정 | N/A | 422 | Results.UnprocessableEntity |
| 선택적 JSON 응답을 사용하여 상태 코드를 400으로 설정 | N/A | 400 | Results.BadRequest |
| 선택적 JSON 응답을 사용하여 상태 코드를 409로 설정 | N/A | 409 | Results.Conflict |
| 응답에 문제 세부 정보 JSON 개체 쓰기 | N/A | 500(기본값), 구성 가능 | Results.Problem |
| 유효성 검사 오류와 함께 응답에 문제 세부 정보 JSON 개체 쓰기 | N/A | 해당 없음, 구성 가능 | Results.ValidationProblem |
결과 사용자 지정
애플리케이션은 사용자 지정 IResult 형식을 구현하여 응답을 제어할 수 있습니다. 다음 코드는 HTML 결과 형식의 예입니다.
using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
public static IResult Html(this IResultExtensions resultExtensions, string html)
{
ArgumentNullException.ThrowIfNull(resultExtensions);
return new HtmlResult(html);
}
}
class HtmlResult : IResult
{
private readonly string _html;
public HtmlResult(string html)
{
_html = html;
}
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.ContentType = MediaTypeNames.Text.Html;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
return httpContext.Response.WriteAsync(_html);
}
}
이러한 사용자 지정 결과를 더 검색 가능하게 만들려면 Microsoft.AspNetCore.Http.IResultExtensions에 확장 메서드를 추가하는 것이 좋습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
<head><title>miniHTML</title></head>
<body>
<h1>Hello World</h1>
<p>The time on the server is {DateTime.Now:O}</p>
</body>
</html>"));
app.Run();
Authorization
권한 부여 정책을 사용하여 경로를 보호할 수 있습니다. 권한 부여 정책은 [Authorize] 특성을 통해 또는 RequireAuthorization 메서드를 사용하여 선언할 수 있습니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
이전 코드는 RequireAuthorization을 사용하여 작성할 수 있습니다.
app.MapGet("/auth", () => "This endpoint requires authorization")
.RequireAuthorization();
다음 샘플은 정책 기반 권한 부여를 사용합니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly",
b => b.RequireClaim("admin", "true")));
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
app.UseAuthorization();
app.MapGet("/admin", [Authorize("AdminsOnly")] () =>
"The /admin endpoint is for admins only.");
app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
.RequireAuthorization("AdminsOnly");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");
app.Run();
인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용
[AllowAnonymous]는 인증되지 않은 사용자가 엔드포인트에 액세스할 수 있도록 허용합니다.
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
경로는 CORS 정책을 사용하여 CORS를 사용할 수 있습니다. CORS는 [EnableCors] 특성을 통해 또는 RequireCors 메서드를 사용하여 선언할 수 있습니다. 다음 샘플은 CORS를 사용하도록 설정합니다.
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/",() => "Hello CORS!");
app.Run();
using Microsoft.AspNetCore.Cors;
const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () =>
"This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
.RequireCors(MyAllowSpecificOrigins);
app.Run();
자세한 내용은 ASP.NET Core에서 CORS(원본 간 요청) 사용을 참조하세요.
참고하십시오
ASP.NET Core