Microsoft는 2002년에 ASP.NET을 ASP.NET Web Forms라는 이름으로 첫 번째 Web Application개발 Framework를 발표했습니다. 그로부터 몇 년 동안 ASP.NET에는 MVC(Model-View-Controller)나 Web API, SignalR 등 새로운 개발 Framework이 추가되었는데 이들 Framework는 핵심적인 변화 없이(더 정확하게는 System.Web이라는 Assembly) 새로운 기능만을 통해 빠르게 발전해 왔습니다. 그러다가 성능문제와 함께 IIS(Microsoft에 의해 개발된 Windows전용 Web Server) 외에 ASP.NET이 구동될 수 있는 환경, Cloud 개발대응성 증가, 더 유연성 있는 환경설정등의 요구사항에 직면하게 되었고 그 결과 ASP.NET Core가 등장하게 되었습니다.
ASP.NET Core는 ASP.NET Framework의 완벽한 개선판이며 다양한 유형의 Application을 개발할 수 있도록 지원하고 있습니다.
- MVC, Razor Page, Blazor를 통한 Single-Page App등 Web Application개발
- API (REST API, RPC(Remote Procedure Call 등)
- Windows Service(또는 Unix Daemon)나 ASP.NET Core Application안에서의 Background 작업 실행
ASP.NET Core는 더이상더 이상 ASP.NET Web Forms와 SOAP기반 Web Service Framework인 Windows Communication Foundation(WCF)를 더 이상 지원하지 않습니다.(다만 WCF는 2022년부터 CoreWCF라는 Project가 진행 중입니다. https://devblogs.microsoft.com/dotnet/corewcf-v1-released/)
여기서 우리는 ASP.NET Core의 기본적인 사항들과 REST API를 개발하는데 사용되는 아래 Application 유형에 대해 알아볼 것입니다.
- ASP.NET Core Web API
- ASP.NET Core minimal API
1. ASP.NET Core 기초사항
ASP.NET Core에 대해 알아보기 전에 몇가지 기본적인 사항들을 짚고 넘어가 보도록 하겠습니다. 이러한 기본적인 것들은 우리가 원하는 Web Application을 구축할 때 그에 필요한 지식기반이 되어줄 것입니다.
ASP.NET Core Application에서 Application의 주요 진입점이 되는 것은 아래와 같은 Program.cs file입니다. 이 file은 WebApplication의 정적 Method인 CreateBuilder를 통해 WebApplicationBuilder를 Instance화 함으로써 Application의 생성을 시작하게 됩니다. 또한 WebApplicationBuilder에서는 필요한 Component를 추가하고 이들을 설정함으로써 원하는 만큼 Application을 확장할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Program.cs file은 ASP.NET Core 8 template에 의해 기본적으로 생성된 file입니다. 특히 이 file은 C#의 top level 구문이라는 기능을 통해 구현되었으며 당장 보이지는 않지만 Global Usings이라는 기능역시 사용되었습니다.
Program.cs는 다음 2개의 부분으로 구분해 볼 수 있습니다.
Service 설정 | Application의 유형과 third-party Library, 인증 및 허가, 의존성 주입을 통한 Service등록을 수행합니다. |
Service 활성화 | ASP.NET Core Middleware pipeline 정의합니다. Middleware는 일단 Application과 결합되면 요청과 응답을 처리하고 다음 Component전/후에 특정 동작을 수행하는 Component라 할 수 있습니다. |
Service설정은 builder.Build() Method를 통해 app이 build되기전 file의 시작 부분에서 구현되어야 하며 Service가 활성화되기 위한 설정은 app.Run() method가 호출되기 전에 이루어져야 합니다. 아래예제는 ASP.NET Core Razor Pages Application이 설정되는 간단한 예시를 나타내고 있습니다.
var builder = WebApplication.CreateBuilder(args);
// Services 구성
builder.Services.AddRazorPages();
var app = builder.Build();
// Services 설정
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
처음 시작할때는 위 예제가 막연하게 느껴질 수도 있지만 ASP.NET Core의 Architecture를 하나씩 알아가다 보면 어렵지 않게 이해할 수 있을 것입니다.
ASP.NET Core architecture | |
Entry Point | - Web Host의 생성 및 구성 - Component를 구성 및 Web Host 실행 |
Service 구성 | - 요청 Service 활성화 (Controller, Razor Page등) - app을 통한 선택적 Service 활성화 (third party Library등) - 의존성 주입을 통한 Service 등록 |
요청 Pipeline 구성 | - 예외 Page 적용 - HTTPS Redirection 적용 - 정적 file처리 적용 - 권한 적용 - 인증 적용 - Endpoint를 적용하고 적절한 Code를 실행 * 요청 처리는 위에서 아래로, 응답은 아래서 위의 순서로 실행 |
위의 Architecture에서 가장 중요한 것은 ASP.NET Core의 중심이라 할 수 있는 의존성 주입이므로 이를 잘 이해하고 있을 필요가 있습니다. 의존성 주입(Dependency Injection)은 개체와 Service class를 서로 느슨하게 결합하는 기술입니다.
이것은 생성자 Method를 통해 Service의 Instance를 직접 생성하기보다는 의존성에서 필요로 하는 Class를 선언하는 방식이며 Service는 자신이 구현된 Interface를 통해 설정되고 Interface는 그들이 Service를 호출하는 Class의 생성자를 통해 주입되어 사용됩니다. 다시 말해 Interface를 통해 개체와 Class 간 느슨한 결합을 가능하게 하는 것으로 code를 추상화하고 test를 용이하게 합니다.
향후에는 Code를 TEST하고 의존성주입을 실제 사용해 봄으로서 이들을 통해 어떤 이점을 얻을 수 있는지는 곧 확인할 수 있는 기회가 있을 것입니다. 의존성을 통해 주입되는 Service의 생명주기도 주목해 볼 필요가 있는데 주입된 Service에 따라 HTTP 요청 Context에 따라 한 번만 사용되어야 하거나 어려번 사용되어야 하는 경우가 있고 Server로 HTTP요청을 하는 모든 사용자에 대해 한번만 사용되어야 하는 경우도 있습니다. ASP.NET은 이러한 3가지 생명주기를 아래와 같이 지원하고 있습니다.
Transient | 들어오는 모든 요청마다 새로운 Service Instance를 생성합니다. 다시 말해 동일하게 들어오는 HTTP요청이라고 하더라도 각 HTTP요청마다 동일한 Servcie의 새로운 Instance를 처리하게 됩니다. |
Scoped | Service는 들어오는 요청당 한번씩만 Instance가 생성됩니다. 가장많이 사용되는 생명주기 방식으로 사용자당 Service Instance의 고유성을 보장합니다. |
Singleton | Service는 Application의 생명주기동안 한번만 Instance를 생성하는 것으로 모든 사용자가 해당 Instance를 공유하게 됩니다. 의존성 주입 Container안에서 Service를 정확히 등록한다면 ASP.NET Core는 이를 관리하게 되므로 thread에 안전해질 수 있습니다(개체 구조체 사용). 그러나 Dictionary와 같은 속성의 변경이 필요한 경우 ConcurrentDictionary를 대신 사용해야 합니다. |
아래 예제에서는 3가지 서로 다른 생명주기를 어떻게 설정할 수 있는지를 확인할 수 있습니다. 여기서 왼쪽 매개변수가 Interface이며 오른쪽 매개변수가 해당 Interface를 구현하는 구체화 Class에 해당합니다. 만약 Class가 생성자를 통해 주입되어야 하는 Interface를 구현하지 않는다면 Compile Error가 발생할 것입니다.
var builder = WebApplication.CreateBuilder(args);
services.AddControllers();
services.AddSingleton<ISingletonService, SingletonService>();
services.AddScoped<IScopedService, ScopedService>();
services.AddTransient<ITransientService, TransientService>();
var app = builder.Build();
...생략
아래 예제에서는 Program.cs에서 위와 같이 연결되는 구체화 구현 Class가 등록된 이후에 MVC Controller에서 어떻게 해당 Service를 주입할 수 있는지를 확인할 수 있습니다.
public class DemoController : Controller
{
private readonly ISingletonService _singletonService;
private readonly IScopedService _scopedService;
private readonly ITransientService _transientService;
public DemoController(ISingletonService singletonService, IScopedService scopedService, ITransientService transientService)
{
_singletonService = singletonService;
_scopedService = scopedService;
_transientService = transientService;
}
}
필요에 따라 Singleton, Scoped, Transient와 같은 Service를 사용할 수 있으므로 아래와 같은 범위 계층구조에 대해 알아두면 사용할 Service를 선택하는데 도움이 될 것입니다.
Transient Service는 Singleton Service와 Scoped Service에 직접적으로 접근할 수 있으며 Scoped Service는 다시 Singleton Server에 직접 접근이 가능합니다. 하지만 그 반대는 불가능한데 다른 것보다 더 긴 생명주기를 가진 개체는 직접 접근이 불가능하기 때문입니다.
ASP.NET Core는 Application안에서 확장기능을 추가할 수 있는 방법을 제공하고 있고 이는 주로 의존성 주입을 통해 이루어집니다. 그리고 여기에 필요한 설정은 appSettins.json안에서 저장하고 관리할 수 있습니다. 심지어는 환경에 따라 달라질 설정까지도 처리할 수 있는데 예를 들어 개발환경이라면 appSettings.development.json file에서 개발단계에 필요한 설정들을 포함시킬 수 있습니다. 만약 동일한 key의 설정이 기본 appSettings.json에 정의되어 있다면 개발 Mode는 appSettings.development.json이 우선하므로 해당 file의 설정값이 appSettings.json의 값을 재정의하게 됩니다. 설정값은 Options Pattern이라는 Option 개체를 생성하여 가져올 수 있습니다. 아래 예제는 appSettings.json에서의 SMTP설정을 예시로 나타내고 있습니다.
{
"SmtpConfig": {
"SmtpAddr": "smtp.gmail.com",
"Port": 465
}
}
이것은 아래와 같이 SmtpConfiguration개체와 연결될 수 있을 것입니다.
public record class SmtpConfig
{
public string SmtpAddr { get; init; }
public int Port { get; init; }
}
해당 설정과 개체를 의존성주입을 통해 어떻게 사용할 수 있는지는 아래 예제를 통해 확인하실 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
services.Configure<SmtpConfiguration>(Configuration.GetSection("SmtpConfig"));
마지막으로 DemoController에서는 IOptions<TOptions> interface를 통해 아래와 같이 의존성이 주입될 수 있습니다.
public class DemoController : Controller
{
private readonly SmtpConfiguration _smtpConfig;
public DemoController(IOptions<SmtpConfig> smtpConfig)
{
_smtpConfig = smtpConfig.Value;
}
}
이러한 방식은 ASP.NET Core에서 option설정을 어떻게 사용할 수 있는지를 비교적 간단한 방법으로 나타낸 것입니다. 물론 필요에 따라 다른 IOptions-를 활용할 수도 있을 것입니다. 이에 관한 더 자세한 사항은 Microsoft의 공식문서를 통해 확인해 보시기 바랍니다.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-8.0
마지막으로 알아둬야할 중요한 것은 ASP.NET Core에서 개발 Mode를 설정하는 것입니다. 개발 Mode에서 개발자는 Application의 동작을 다르게 구성할 수 있습니다. 이를 테면 실제 운영환경에서는 암호화된 연결문자열을 사용해야 하지만 개발환경에서는 굳이 암호화설정이 필요 없을 수 있습니다. 또는 처리되지 않은 예외가 발생하는 경우 이에 대한 상세한 정보를 표시하는 것은 개발 Mode에 이루어져야 하며 실제 운영환경에서 오류에 대한 자세한 정볼르 표시하는 것은 불필요할뿐더러 자칫 보안적인 이슈사항이 발생할 수도 있습니다. 따라서 개발자는 Application이 실제 운영환경에 들어가게 되면 개발모드를 더 이상 사용하지 않도록 주의해 줄 필요가 있습니다. 이러한 설정동작을 실제 사용하기 위해서는 launchSettings.json file 또는 Project 속성설정화면에서 ASPNETCORE_ENVIRONMENT 환경변수설정을 Development로 설정해야 합니다. 환경변수와 암호화 연결 문자열에 관한 구체적인 예는 곧 확인해 볼 수 있을 것입니다. 아래 예제는 LaunchSettings.json file에서 SSL이나 applicationUrl설정과 함께 개발 Mode가 어떻게 설정될 수 있는지를 나타내고 있습니다.
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57090",
"sslPort": 44366
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
...생략
}
2. AP.NET Core Web API
ASP.NET Core Web API는 Web API를 생성하기 위한 것이며 Web API는 HTTP를 통해 사용되는 Application Programming Interface(API)라 할 수 있습니다. 현재는 JSON(Javascript Object Notation)형식및 XML(Extensible Markup Language)과 관련이 깊은 REST를 통해 구현되고 있으며 이때 API는 URI(Uniform Resource Identifier)와 같은 HTTP기능을 사용하게 됩니다.
최종 Internet 사용자는 다양한 Terminal을 사용할 수 있기 때문에 빠르고 안전한 방법으로 Browser나 최신의 Device에 Data를 제공해줄 필요가 있고 따라서 Web API는 이들에게서 호환이 가능하도록 만들어져야 합니다. ASP.NET Core Web API는 다수의 사용자가 사용할 수 있는 Web Service를 개발하는데 가장 적절한 고성능 Framework입니다.
ASP.NET Core Web API는 MVC(Model-View-Controller) Pattern을 따르고 있는데 암시적으로 MVC에서 View는 Web Page를 말하지만 API에서는 JSON 또는 XML이나 기타 특정 Format Data를 응답합니다.
여기서 작성되는 모든 예제는 Visual Studio를 사용합니다. 다만 동일한 결과물은 .NET CLI(Ccommand-Line Interface)를 통해서도 가능하며 아래 Link를 통해 관련 내용을 참고하실 수 있습니다.
https://learn.microsoft.com/en-us/dotnet/core/tools/
Visual Studio 2022(이하 2022는 생략됨)를 통해 어떻게 Web API를 생성할 수 있는지 간단히 살펴보도록 하겠습니다. 우선 Visual Studio를 실행하고 아래와 같이 Drop Down List에서 Web을 선택하여 ASP.NET Core Web API항목을 선택합니다.
그다음 화면을 통해서는 Project의 이름과 실제 Project의 File이 저장될 경로, 그리고 Solution 이름을 설정합니다.
이후에는 생성할 Application에 대한 몇 가지 설정을 진행할 수 있는데, 특히 이 부분에서는 ASP.NET Core Web API를 실행할 Runtime을 .NET 8(혹은 그 이후 Version)을 선택하도록 합니다. 그리고 그 밖에 인증 유형(Windows, Microsoft Identity Platform, 비 인증유형)과 HTTPS, Docker관련, OpenAPI사용여부, Controller의 사용여부(사용하지 않을 경우 Minimal API를 대신 사용)등을 지정할 수 있습니다.
다음으로 넘어가기 전에 'Enable OpenAPI support'부분을 Check하여 OpenAPI를 사용할 수 있도록 합니다.
Docker는 개발자가 자신의 Application의 Source Code와 Application실행에 필요한 전체 OS(Operating System) Library 그리고 의존성을 결합시켜 Container화 할 수 있도록 해주는 Open Source Platform입니다. Docker는 한 번쯤은 꼭 사용해 보길 권합니다.
https://www.docker.com/why-docker/
Open API의 경우 언어에 구애받지 않는 RESTFul API에 대한 표준 Interface 사양을 정의하여 사용자(혹은 Machine)가 Source Code를 읽지 않더라도 Service에 대한 기능과 찾고 사용법을 이해할 수 있도록 지원할 수 있습니다. 자세한 사항은 아래 Link에서 참고할 수 있는데
https://swagger.io/specification/
Swagger는 Open API위에서 API를 설명하는 일련의 도구라 할 수 있습니다.
이제 Create Button을 눌러보면 Visual Studio는 기본 Template을 통해 WeatherForecast Model과 Controller를 포함하는 Project를 생성할 것입니다. 아래 화면은 Visual Studio에 의해 생성된 기본 Project의 형태를 보여주고 있습니다.
Swagger와 함께 가벼우면서도 Cross-Platform인 HttpRepl(HTTP Real-Eval-Print Loop)이라는 Command-Line도구를 사용할 수 있습니다. ASP.NET Core API뿐만 아니라 다른 종류의 API에서도 사용될 수 있는 이 도구는 DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT 등 다양한 Http Method의 HTTP요청을 만들고 API가 정상적으로 Host 된다면 그 결과를 바로 확인할 수 있습니다.
HttpRepl을 설치하기 위해서는 PowerShell의 명령줄 도구를 사용해 아래 명령을 실행합니다.
dotnet tool install -g Microsoft.dotnet-httprepl |
HttpRepl에서 사용가한 모든 명령을 확인해 보려면 --help option을 사용합니다.
httprepl --help |
아래 화면은 API에서 가능한 EndPoint를 확인하고 실행하는 과정을 보여주고 있습니다. EndPoint는 swagger.json file을 읽음으로써 확인할 수 있는데 이는 아래 명령을 통해 자동적으로 완성될 수 있습니다.
httprepl https://localhost:7162 |
EndPoint를 확인하고 실행할때 httprepl은 EndPoint를 나열하기 위해 ls(Windows에서 Directory의 File을 나열하는) 혹은 해당 EndPoint로 위치하기 위해 cd(Windows상에서 Directory이동) 명령과 같은 MS-DOS를 사용합니다.
위와 같은 HttpRepl외에도 친숙한 UI로 사용할 수 있는 Postman이라는 Tool도 사용할 수 있습니다. Postman도 HttpRepl과 동일하게 HTTP요청을 생성하고 이를 통해 주어진 API의 EndPoint를 TEST 할 수 있습니다. 특히 가능한 URL, Header, verbe, query string, body와 같이 모든 요청 매개변수를 설정할 수 있습니다. 해당 도구는 http://www.postman.com에서 내려받을 수 있습니다. 현재 많은 개발자가 Postman을 사용하고 있지만 실제 우리가 만든 API를 TEST할때는 HttpRepl을 사용할 것입니다.
3. ASP.NET Core Minimal API
ASP.NET Core 8에서는 Minimal API라는 새로운 기능을 도입하였습니다. 기존 API Platform을 놔두고 왜 Minimal API라는 것을 도입했을까? 이름에서 알 수 있듯이 그 용도는 아주 명확합니다. 때로는 필요한 Data를 활용하기 위해 하나 또는 두 개 정도의 EndPoint를 가진 소규모 API를 작성해야 하는 경우가 있습니다. 단순히 EndPoint가 작다는 것에서 끝나는 것이 아니고 구조적으로 필요한 Controller를 구현하지 않고 Program.cs라는 단 하나의 파일 안에서만 필요한 기능이 구현됩니다.
최소한의 설정과 함께 자체적으로 Application을 실행할 수 있으며 모든 ASP.NET Core Pipeline은 동일하게 유지됩니다. 즉, 의존성주입 System과 서로 연결되어 HTTP 요청과 응답을 관리하는 Middleware를 의미합니다.
Minimal API의 Project를 생성하기 위해 아래와 같이 'AP.NET Core Empty' Project를 Template을 선택합니다.
ASP.NET Core Web API Template을 사용하는 경우 'Use controllers'부분을 Check해제하면 동일한 결과를 얻을 수 있습니다.
이 후 Project이름을 설정하고 나면 Visual Studio는 'Hello World!'를 출력하는 기본 EndPoint를 가진 최소한의 Project를 아래와 같이 생성하게 됩니다.
최소한의 구현으로 시작하지만 이 상태에서도 Program.cs의 EndPoint를 사용자가 확인할 수 있도록 Swagger를 제공하고 필요한 경우 의존성 주입 역시 사용할 수 있도록 구현할 수 있습니다. 또한 C#을 통해 Lambda표현식으로 속성을 수식하는 등의 최신기능 역시 사용할 수 있습니다.
일부 개발자는 위와 같이 최소한으로 시작하여 조금씩 필요한 기능을 처음으로 하나씩 부여하는 방법을 선호하기도 합니다.
'.NET > ASP.NET' 카테고리의 다른 글
[ASP.NET Core 8] REST API - HTTP와 REST (1) | 2024.10.22 |
---|---|
NET::ERR_CERT_INVALID 문제 (0) | 2023.11.27 |
ASP.NET Core - 21. ASP.NET Core Identity 사용 (2) | 2023.04.09 |
ASP.NET Core - 20. ASP.NET Core Identity (0) | 2023.04.04 |
ASP.NET Core - [Blazor] 6. DataBlazor Web Assembly (0) | 2023.03.29 |