ASP.NET Core의 HTTP 로깅
참고 항목
이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.
Important
이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.
현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.
HTTP 로깅은 들어오는 HTTP 요청 및 HTTP 응답에 대한 정보를 기록하는 미들웨어입니다. HTTP 로깅은 다음의 로그를 제공합니다.
- HTTP 요청 정보
- 일반 속성
- 머리글
- 본문
- HTTP 응답 정보
HTTP 로깅은 다음을 수행할 수 있습니다.
- 모든 요청 및 응답 또는 특정 조건을 충족하는 요청 및 응답만 기록합니다.
- 요청 및 응답이 기록되는 부분을 선택합니다.
- 로그에서 중요한 정보를 수정할 수 있습니다.
HTTP 로깅 은 특히 요청 및 응답 본문을 로깅할 때 앱의 성능을 저하시킬 수 있습니다. 로그할 필드를 선택할 때 성능에 미치는 영향을 고려하고 선택한 로깅 속성이 성능에 미치는 영향을 테스트하세요.
Warning
HTTP 로깅은 PII(개인 식별 정보)를 기록할 수 있습니다. 위험성을 고려해 중요한 정보를 로그하지 않도록 하세요.
HTTP 로깅 사용
HTTP 로깅은 다음 예제와 같이 호출 AddHttpLogging 하여 사용하도록 설정됩니다 UseHttpLogging.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(o => { });
var app = builder.Build();
app.UseHttpLogging();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.MapGet("/", () => "Hello World!");
app.Run();
이전 호출 AddHttpLogging
예제의 빈 람다는 기본 구성을 사용하여 미들웨어를 추가합니다. 기본적으로 HTTP 로깅은 요청 및 응답에 대한 경로, 상태 코드 및 헤더와 같은 일반적인 속성을 기록합니다.
HTTP 로그가 표시되도록 "LogLevel": {
수준에서 다음 줄을 appsettings.Development.json
파일에 추가:
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
기본 구성을 사용하면 요청 및 응답이 다음 예제와 유사한 메시지 쌍으로 기록됩니다.
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/2
Method: GET
Scheme: https
PathBase:
Path: /
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Host: localhost:52941
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: [Redacted]
sec-ch-ua: [Redacted]
sec-ch-ua-mobile: [Redacted]
sec-ch-ua-platform: [Redacted]
sec-fetch-site: [Redacted]
sec-fetch-mode: [Redacted]
sec-fetch-user: [Redacted]
sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8
Date: Tue, 24 Oct 2023 02:03:53 GMT
Server: Kestrel
HTTP 로깅 옵션
HTTP 로깅 미들웨어에 대한 전역 옵션을 구성하려면 람다를 사용하여 호출 AddHttpLogging Program.cs
하여 구성 HttpLoggingOptions합니다.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
참고 항목
앞의 샘플 및 다음 샘플 UseHttpLogging
에서는 정 UseStaticFiles
적 파일에 대해 HTTP 로깅을 사용할 수 없도록 호출됩니다. 정적 파일 HTTP 로깅을 사용하도록 설정하려면 앞에 호출 UseHttpLogging
합니다 UseStaticFiles
.
LoggingFields
HttpLoggingOptions.LoggingFields
는 로그 요청 및 응답의 특정 부분을 구성하는 열거형 플래그입니다. HttpLoggingOptions.LoggingFields
기본값은 RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders입니다.
RequestHeaders
및 ResponseHeaders
RequestHeaders 로 ResponseHeaders 깅되는 HTTP 헤더 집합입니다. 헤더 값은 이러한 컬렉션에 있는 헤더 이름에 대해서만 기록됩니다. 헤더 값 sec-ch-ua
이 RequestHeaders기록되도록 다음 코드가 추가 sec-ch-ua
됩니다. MyResponseHeader
헤더 값 MyResponseHeader
이 ResponseHeaders기록되도록 추가됩니다. 이러한 줄이 제거되면 이러한 헤더의 값은 다음과 같습니다 [Redacted]
.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
MediaTypeOptions
MediaTypeOptions는 특정 미디어 유형에 사용할 인코딩을 선택하기 위한 구성을 제공합니다.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
이 방법은 기본적으로 기록되지 않은 데이터에 대한 로깅을 사용하도록 설정하는 데도 사용할 수 있습니다(예: 양식 데이터, 미디어 형식 또는 multipart/form-data
같은 application/x-www-form-urlencoded
형식이 있을 수 있음).
MediaTypeOptions
메서드
RequestBodyLogLimit
및 ResponseBodyLogLimit
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
CombineLogs
요청 및 응답에 true
대해 사용하도록 설정된 모든 로그를 마지막에 하나의 로그에 통합하도록 미들웨어를 구성하도록 설정합니다CombineLogs. 여기에는 요청, 요청 본문, 응답, 응답 본문 및 기간이 포함됩니다.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
엔드포인트별 구성
최소 API 앱의 엔드포인트별 구성의 경우 확장 메서드를 WithHttpLogging 사용할 수 있습니다. 다음 예제에서는 하나의 엔드포인트에 대해 HTTP 로깅을 구성하는 방법을 보여 줍니다.
app.MapGet("/response", () => "Hello World! (logging response)")
.WithHttpLogging(HttpLoggingFields.ResponsePropertiesAndHeaders);
컨트롤러를 사용하는 앱의 엔드포인트별 구성의 [HttpLogging]
경우 특성을 사용할 수 있습니다. 이 특성은 다음 예제와 같이 최소 API 앱에서도 사용할 수 있습니다.
app.MapGet("/duration", [HttpLogging(loggingFields: HttpLoggingFields.Duration)]
() => "Hello World! (logging duration)");
IHttpLoggingInterceptor
IHttpLoggingInterceptor 는 기록되는 세부 정보를 사용자 지정하기 위해 요청당 및 응답별 콜백을 처리하도록 구현할 수 있는 서비스의 인터페이스입니다. 엔드포인트별 로그 설정이 먼저 적용된 다음 이러한 콜백에서 재정의될 수 있습니다. 구현은 다음을 수행할 수 있습니다.
- 요청 또는 응답을 검사합니다.
- 을 사용하거나 사용하지 않도록 설정합니다 HttpLoggingFields.
- 기록되는 요청 또는 응답 본문의 양을 조정합니다.
- 로그에 사용자 지정 필드를 추가합니다.
에서 IHttpLoggingInterceptor
호출 AddHttpLoggingInterceptor<T>
하여 구현을 등록합니다 Program.cs
. 여러 IHttpLoggingInterceptor
인스턴스가 등록된 경우 등록된 순서대로 실행됩니다.
다음 예제에서는 구현을 등록하는 IHttpLoggingInterceptor
방법을 보여줍니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.Duration;
});
builder.Services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();
다음 예제는 구현입니다.IHttpLoggingInterceptor
- 요청 메서드를 검사하고 POST 요청에 대한 로깅을 사용하지 않도록 설정합니다.
- POST가 아닌 요청의 경우:
- 요청 경로, 요청 헤더 및 응답 헤더를 수정합니다.
- 요청 및 응답 로그에 사용자 지정 필드 및 필드 값을 추가합니다.
using Microsoft.AspNetCore.HttpLogging;
namespace HttpLoggingSample;
internal sealed class SampleHttpLoggingInterceptor : IHttpLoggingInterceptor
{
public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext)
{
if (logContext.HttpContext.Request.Method == "POST")
{
// Don't log anything if the request is a POST.
logContext.LoggingFields = HttpLoggingFields.None;
}
// Don't enrich if we're not going to log any part of the request.
if (!logContext.IsAnyEnabled(HttpLoggingFields.Request))
{
return default;
}
if (logContext.TryDisable(HttpLoggingFields.RequestPath))
{
RedactPath(logContext);
}
if (logContext.TryDisable(HttpLoggingFields.RequestHeaders))
{
RedactRequestHeaders(logContext);
}
EnrichRequest(logContext);
return default;
}
public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext)
{
// Don't enrich if we're not going to log any part of the response
if (!logContext.IsAnyEnabled(HttpLoggingFields.Response))
{
return default;
}
if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders))
{
RedactResponseHeaders(logContext);
}
EnrichResponse(logContext);
return default;
}
private void RedactPath(HttpLoggingInterceptorContext logContext)
{
logContext.AddParameter(nameof(logContext.HttpContext.Request.Path), "RedactedPath");
}
private void RedactRequestHeaders(HttpLoggingInterceptorContext logContext)
{
foreach (var header in logContext.HttpContext.Request.Headers)
{
logContext.AddParameter(header.Key, "RedactedHeader");
}
}
private void EnrichRequest(HttpLoggingInterceptorContext logContext)
{
logContext.AddParameter("RequestEnrichment", "Stuff");
}
private void RedactResponseHeaders(HttpLoggingInterceptorContext logContext)
{
foreach (var header in logContext.HttpContext.Response.Headers)
{
logContext.AddParameter(header.Key, "RedactedHeader");
}
}
private void EnrichResponse(HttpLoggingInterceptorContext logContext)
{
logContext.AddParameter("ResponseEnrichment", "Stuff");
}
}
이 인터셉터를 사용하면 HTTP 로깅이 로깅되도록 구성된 경우에도 POST 요청이 로그 HttpLoggingFields.All
를 생성하지 않습니다. GET 요청은 다음 예제와 유사한 로그를 생성합니다.
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Path: RedactedPath
Accept: RedactedHeader
Host: RedactedHeader
User-Agent: RedactedHeader
Accept-Encoding: RedactedHeader
Accept-Language: RedactedHeader
Upgrade-Insecure-Requests: RedactedHeader
sec-ch-ua: RedactedHeader
sec-ch-ua-mobile: RedactedHeader
sec-ch-ua-platform: RedactedHeader
sec-fetch-site: RedactedHeader
sec-fetch-mode: RedactedHeader
sec-fetch-user: RedactedHeader
sec-fetch-dest: RedactedHeader
RequestEnrichment: Stuff
Protocol: HTTP/2
Method: GET
Scheme: https
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
Content-Type: RedactedHeader
MyResponseHeader: RedactedHeader
ResponseEnrichment: Stuff
StatusCode: 200
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
ResponseBody: Hello World!
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[8]
Duration: 2.2778ms
우선 순위의 로깅 구성 순서
다음 목록에서는 로깅 구성의 우선 순위 순서를 보여 줍니다.
- 에서 전역 구성 HttpLoggingOptions, 호출 AddHttpLogging을 통해 설정합니다.
- 특성 또는 확장 메서드의
[HttpLogging]
엔드포인트별 구성이 전역 구성을 재정의 WithHttpLogging 합니다. IHttpLoggingInterceptor
는 결과와 함께 호출되며 요청당 구성을 추가로 수정할 수 있습니다.
HTTP 로깅은 들어오는 HTTP 요청 및 HTTP 응답에 대한 정보를 로그하는 미들웨어입니다. HTTP 로깅은 다음의 로그를 제공합니다.
- HTTP 요청 정보
- 일반 속성
- 머리글
- 본문
- HTTP 응답 정보
HTTP 로깅이 유용한 몇 가지 시나리오는 다음과 같습니다.
- 들어오는 요청 및 응답에 대한 정보를 기록합니다.
- 요청 및 응답의 어느 부분이 로그되는지 필터링합니다.
- 로그할 헤더를 필터링합니다.
HTTP 로깅은 특히 요청 및 응답 본문을 로그할 때 앱의 성능을 저하시킬 수 있습니다. 로그할 필드를 선택할 때 성능에 미치는 영향을 고려하고 선택한 로깅 속성이 성능에 미치는 영향을 테스트하세요.
Warning
HTTP 로깅 사용 시 PII(개인 식별 정보)가 로그될 가능성이 있습니다. 위험성을 고려해 중요한 정보를 로그하지 않도록 하세요.
HTTP 로깅 사용
HTTP 로깅은 HTTP 로깅 미들웨어를 추가하는 UseHttpLogging을 통해 사용할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpLogging();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.MapGet("/", () => "Hello World!");
app.Run();
기본적으로 HTTP 로깅은 요청 및 응답에 대한 경로, 상태 코드, 헤더와 같은 공통 속성을 로그합니다. HTTP 로그가 표시되도록 "LogLevel": {
수준에서 다음 줄을 appsettings.Development.json
파일에 추가:
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
출력은 LogLevel.Information
에서 단일 메시지로 로그됩니다.
HTTP 로깅 옵션
HTTP 로깅 미들웨어를 구성하려면 Program.cs
에서 AddHttpLogging을 호출합니다.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
참고 항목
앞의 샘플 및 다음 샘플 UseHttpLogging
에서는 정 UseStaticFiles
적 파일에 대해 HTTP 로깅을 사용할 수 없도록 호출됩니다. 정적 파일 HTTP 로깅을 사용하도록 설정하려면 앞에 호출 UseHttpLogging
합니다 UseStaticFiles
.
LoggingFields
HttpLoggingOptions.LoggingFields
는 로그 요청 및 응답의 특정 부분을 구성하는 열거형 플래그입니다. HttpLoggingOptions.LoggingFields
기본값은 RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders입니다.
RequestHeaders
Headers는 로그할 수 있는 HTTP 요청 헤더의 집합입니다. 헤더 값은 이 컬렉션에 있는 헤더 이름에 대해서만 로그됩니다. 다음 코드는 요청 헤더 "sec-ch-ua"
를 로그합니다. logging.RequestHeaders.Add("sec-ch-ua");
가 제거된 경우 요청 헤더 "sec-ch-ua"
의 값이 수정됩니다. 다음 강조 표시된 코드는 HttpLoggingOptions.RequestHeaders
및 HttpLoggingOptions.ResponseHeaders
를 호출합니다.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
MediaTypeOptions
MediaTypeOptions는 특정 미디어 유형에 사용할 인코딩을 선택하기 위한 구성을 제공합니다.
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
이 방법을 사용하여 기본적으로 기록되지 않은 데이터에 대한 로깅을 사용하도록 설정할 수도 있습니다. 예를 들어 미디어 형식(예: application/x-www-form-urlencoded
또는 multipart/form-data
.)을 가질 수 있는 양식 데이터입니다.
MediaTypeOptions
메서드
RequestBodyLogLimit
및 ResponseBodyLogLimit
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});
app.MapGet("/", () => "Hello World!");
app.Run();
ASP.NET Core