.NET CLI를 사용한 초기 프로젝트 설정부터 미들웨어, 컨트롤러 및 서비스 구성까지 강력한 API를 구축하기 위한 모든 단계를 알아보세요. 확장 가능하고 효율적인 웹 애플리케이션을 만들기 위한 종속성 주입, 비동기 작업 및 예외 처리에 대한 모범 사례를 살펴보세요.
.NET CLI를 사용하여 새 Web API 프로젝트를 만듭니다. 이는 시작을 위한 Program.cs와 WeatherForecast 컨트롤러를 예로 들어 기본 프로젝트 구조를 설정합니다.
dotnet new webapi -n MyWebApi
.NET 8은 최소한의 API를 향한 추세를 이어가므로 Program.cs 파일에서 직접 간단하고 간결한 방식으로 서비스와 엔드포인트를 구성할 수 있습니다.
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello, World!"); app.Run();
컨트롤러는 들어오는 HTTP 요청을 처리하고 클라이언트에 응답합니다. ControllerBase에서 상속하고 [ApiController]로 주석을 달아 정의됩니다.
[ApiController] [Route("[controller]")] public class MyController : ControllerBase { [HttpGet] public IActionResult Get() => Ok("Hello from MyController"); }
.NET Core에 내장된 DI(종속성 주입)를 사용하면 종속성을 쉽게 관리할 수 있습니다. 생성자를 통해 컨트롤러에 서비스를 주입할 수 있습니다.
public class MyService { public string GetMessage() => "Injected message"; } public class MyController : ControllerBase { private readonly MyService _myService; public MyController(MyService myService) { _myService = myService; } [HttpGet] public IActionResult Get() => Ok(_myService.GetMessage()); }
서비스(예: 데이터베이스 컨텍스트, 사용자 지정 서비스 등)는 Program.cs 파일에 구성되어 애플리케이션 전체에서 종속성 주입에 사용할 수 있습니다.
builder.Services.AddScoped<MyService>();
.NET은 환경별 구성 파일(appsettings.json, appsettings.Development.json 등)을 지원하므로 애플리케이션 환경에 따라 다양한 설정이 가능합니다.
// appsettings.Development.json { "Logging": { "LogLevel": { "Default": "Debug" } } }
미들웨어 구성 요소는 요청과 응답을 처리하는 파이프라인을 형성합니다. 로깅이나 오류 처리와 같은 교차 문제를 위해 사용자 정의 미들웨어를 만들 수 있습니다.
app.Use(async (context, next) => { // Custom logic before passing to the next middleware await next(); // Custom logic after executing the next middleware });
.NET Web API의 라우팅은 컨트롤러 및 작업 메서드의 특성 라우팅을 통해 수행됩니다. 이를 통해 URL을 컨트롤러 작업에 직접 매핑할 수 있습니다.
[HttpGet("myaction/{id}")] public IActionResult GetAction(int id) => Ok($"Action with ID = {id}");
모델 바인딩은 HTTP 요청의 데이터를 작업 메서드 매개 변수에 자동으로 매핑합니다. JSON 본문 및 쿼리 문자열 매개변수를 포함한 복잡한 유형을 지원합니다.
public class MyModel { public int Id { get; set; } public string Name { get; set; } } [HttpPost] public IActionResult PostAction([FromBody] MyModel model) => Ok(model);
데이터 주석을 사용하여 모델 데이터를 검증할 수 있습니다. [ApiController] 속성은 모델이 유효하지 않은 경우 400으로 응답하여 자동으로 유효성 검사를 시행합니다.
public class MyModel { [Required] public int Id { get; set; } [StringLength(100)] public string Name { get; set; } }
비동기 작업은 I/O 작업이 완료되기를 기다리는 동안 스레드를 확보하여 확장성을 향상시킵니다. async 키워드를 사용하고 Task 또는 Task<IActionResult>를 반환합니다.
[HttpGet("{id}")] public async Task<IActionResult> GetAsync(int id) { var result = await _service.GetByIdAsync(id); return Ok(result); }
전역 예외 처리를 통해 중앙 집중식 오류 처리, 로깅 및 처리되지 않은 예외에 대한 표준화된 API 응답이 가능합니다.
app.UseExceptionHandler(a => a.Run(async context => { var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>(); var exception = exceptionHandlerPathFeature.Error; // Log the exception, generate a custom response, etc. context.Response.StatusCode = 500; await context.Response.WriteAsJsonAsync(new { Error = "An unexpected error occurred" }); }));
API 버전 관리는 시간 경과에 따른 API 변경 사항을 관리하는 데 도움이 됩니다. .NET 플랫폼은 쿼리 문자열, URL 경로 또는 요청 헤더를 통한 버전 관리를 지원합니다.
builder.Services.AddApiVersioning(options => { options.AssumeDefaultVersionWhenUnspecified = true; options.DefaultApiVersion = new ApiVersion(1, 0); options.ReportApiVersions = true; });
콘텐츠 협상을 통해 API는 요청의 Accept 헤더를 기반으로 다양한 형식의 응답을 제공하여 JSON, XML 등과 같은 형식을 지원할 수 있습니다.
builder.Services.AddControllers() .AddXmlDataContractSerializerFormatters();
JSON 직렬 변환기 설정을 구성하여 camelCase 이름 지정 또는 null 값 무시와 같은 JSON 응답 형식을 사용자 정의합니다.
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.IgnoreNullValues = true; });
CORS(Cross-Origin Resource Sharing)를 사용하면 다른 도메인에 호스팅된 웹 애플리케이션에서 API를 호출할 수 있습니다. 요구 사항에 따라 CORS 정책을 구성합니다.
builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", builder => builder.WithOrigins("http://example.com")); }); app.UseCors("AllowSpecificOrigin");
요청하는 사용자 또는 서비스의 신원을 확인하는 인증을 활성화하여 API를 보호하세요.
builder.Services.AddAuthentication("Bearer") .AddJwtBearer(options => { options.Authority = "https://your-auth-server"; options.Audience = "your-api"; });
인증 후 권한 부여는 인증된 사용자에게 작업을 수행하거나 리소스에 액세스할 수 있는 권한이 있는지 확인합니다.
[Authorize] public class SecureController : ControllerBase { // Action methods here }
Swagger(OpenAPI)는 개발자가 API를 쉽게 이해하고 사용할 수 있도록 API에 대한 대화형 문서를 제공합니다.
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); app.UseSwagger(); app.UseSwaggerUI();
.NET Core는 다양한 출력(콘솔, 디버그 창, 외부 서비스 등)에 메시지를 기록할 수 있는 기본 제공 로깅 프레임워크를 제공합니다.
logger.LogInformation("This is an informational message"); app.Use(async (context, next) => { logger.LogError("This is an error message before the next middleware"); await next.Invoke(); // Log after calling the next middleware });
Entity Framework Core는 .NET 애플리케이션에서 데이터 액세스에 사용되는 ORM입니다. 이를 통해 강력한 형식의 개체를 사용하여 데이터를 쿼리하고 조작할 수 있습니다.
public class MyDbContext : DbContext { public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) {} public DbSet<MyModel> MyModels { get; set; } }
마이그레이션을 사용하면 데이터 모델의 변경 사항을 추적하여 데이터베이스 스키마에 버전 제어를 적용할 수 있습니다.
dotnet ef migrations add InitialCreate dotnet ef database update
리포지토리 패턴은 데이터 계층을 추상화하여 애플리케이션을 더욱 모듈화하고 유지 관리하기 쉽게 만듭니다.
public interface IRepository<T> { Task<IEnumerable<T>> GetAllAsync(); Task<T> GetByIdAsync(int id); // Other methods... } public class MyRepository<T> : IRepository<T> where T : class { private readonly MyDbContext _context; public MyRepository(MyDbContext context) { _context = context; } // Implement methods... }
단위 테스트는 개별 코드 단위를 별도로 테스트하여 Web API가 올바르게 작동하는지 확인합니다.
public class MyControllerTests { [Fact] public async Task Get_ReturnsExpectedValue() { // Arrange var serviceMock = new Mock<IMyService>(); serviceMock.Setup(service => service.GetAsync()).ReturnsAsync("test"); var controller = new MyController(serviceMock.Object); // Act var result = await controller.Get(); // Assert Assert.Equal("test", result.Value); } }
.NET Web API는 프런트 엔드 애플리케이션의 백엔드 역할을 하여 RESTful 서비스를 제공할 수 있습니다.
fetch('https://localhost:5001/mycontroller') .then(response => response.json()) .then(data => console.log(data));
상태 확인은 애플리케이션 상태와 종속성을 모니터링하는 방법을 제공하며 마이크로서비스 아키텍처에 유용합니다.
builder.Services.AddHealthChecks(); app.MapHealthChecks("/health");
SignalR은 실시간 웹 기능을 지원하므로 서버 측 코드가 클라이언트 측 웹 애플리케이션에 비동기 알림을 보낼 수 있습니다.
public class MyHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }
응답 캐싱은 이전에 요청한 리소스의 복사본을 저장하여 서버가 처리해야 하는 요청 수를 줄입니다.
[HttpGet("{id}")] [ResponseCache(Duration = 60)] public IActionResult GetById(int id) { // Retrieve and return your resource }
.NET Web API를 사용하여 프런트 엔드 애플리케이션을 지원하려면 정적 파일(HTML, CSS, JavaScript 등)을 제공하는 것이 필수적입니다.
app.UseStaticFiles(); // Enable static file serving
옵션 패턴은 클래스를 사용하여 관련 설정 그룹을 나타냅니다. IOptions<T>를 사용하면 응용 프로그램 어디에서나 이러한 설정에 액세스할 수 있습니다.
public class MySettings { public string Setting1 { get; set; } // Other settings } builder.Services.Configure<MySettings>(builder.Configuration.GetSection("MySettings")); public class MyService { private readonly MySettings _settings; public MyService(IOptions<MySettings> settings) { _settings = settings.Value; } // Use _settings.Setting1 }
미들웨어는 요청과 응답을 처리하기 위해 애플리케이션 파이프라인으로 조립되는 소프트웨어입니다. 특정 작업을 수행하기 위해 사용자 정의 미들웨어를 만들 수 있습니다.
public class MyCustomMiddleware { private readonly RequestDelegate _next; public MyCustomMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { // Pre-processing logic here await _next(httpContext); // Call the next middleware in the pipeline // Post-processing logic here } } // Extension method for easy middleware registration public static class MyCustomMiddlewareExtensions { public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<MyCustomMiddleware>(); } }
속도 제한은 특정 시간 내에 사용자가 요청할 수 있는 빈도를 제한하여 API가 과도하게 사용되지 않도록 보호합니다.
// Assume using a third-party library like AspNetCoreRateLimit builder.Services.AddInMemoryRateLimiting(); builder.Services.Configure<IpRateLimitOptions>(options => { options.GeneralRules = new List<RateLimitRule> { new RateLimitRule { Endpoint = "*", Limit = 100, Period = "1h" } }; });
API 키는 API 호출을 인증하고 승인하는 간단한 방법입니다. 쿼리 문자열이나 헤더를 통해 클라이언트에서 서버로 전달됩니다.
public class ApiKeyMiddleware { private readonly RequestDelegate _next; private const string APIKEYNAME = "x-api-key"; public ApiKeyMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (!context.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey)) { context.Response.StatusCode = 401; await context.Response.WriteAsync("API Key was not provided."); return; } // Validate the extracted API Key here... await _next(context); } }
출력 캐싱을 사용하면 요청에 대한 응답을 저장할 수 있습니다. 후속 요청은 캐시에서 처리될 수 있으므로 성능이 크게 향상됩니다.
[HttpGet] [ResponseCache(Duration = 120, Location = ResponseCacheLocation.Client, NoStore = false)] public IActionResult Get() { // Your logic here }
백그라운드 작업을 사용하면 이메일 보내기, 장기 실행 작업 처리 등 사용자 요청과 관계없이 백그라운드에서 작업을 실행할 수 있습니다.
public class MyBackgroundService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // Your background task logic here await Task.Delay(TimeSpan.FromHours(1), stoppingToken); } } }
WebSocket은 실시간 애플리케이션에 이상적인 단일 장기 연결을 통해 전이중 통신 채널을 제공합니다.
app.UseWebSockets(); app.Use(async (context, next) => { if (context.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); // Handle the WebSocket request here } else { await next(); } });
요청 지역화는 요청 정보를 기반으로 다양한 문화와 언어에 맞게 콘텐츠를 지역화하는 방법을 제공합니다.
var supportedCultures = new[] { "en-US", "fr-FR" }; var localizationOptions = new RequestLocalizationOptions() .SetDefaultCulture(supportedCultures[0]) .AddSupportedCultures(supportedCultures) .AddSupportedUICultures(supportedCultures); app.UseRequestLocalization(localizationOptions);
GraphQL은 API용 쿼리 언어입니다. .NET 웹 API를 GraphQL과 통합하면 보다 효율적인 데이터 검색이 가능합니다.
// Assume using a library like HotChocolate builder.Services .AddGraphQLServer() .AddQueryType<Query>(); app.MapGraphQL();
모니터링 및 원격 측정에는 애플리케이션의 성능 및 사용량에 대한 데이터 수집, 분석 및 조치가 포함됩니다.
// Assume using Application Insights builder.Services.AddApplicationInsightsTelemetry("YOUR_INSTRUMENTATION_KEY");
SignalR은 앱에 실시간 웹 기능 추가를 단순화하는 라이브러리입니다. 실시간 웹 기능은 클라이언트가 새 데이터를 요청할 때까지 서버가 기다릴 필요 없이 서버 코드가 연결된 클라이언트에 즉시 콘텐츠를 푸시하도록 하는 기능입니다. SignalR은 채팅 애플리케이션, 실시간 대시보드 및 보다 대화형인 웹 애플리케이션을 개발하는 데 적합합니다.
public class ChatHub : Hub { public async Task SendMessage(string user, string message) { // Call the broadcastMessage method to update clients. await Clients.All.SendAsync("broadcastMessage", user, message); } } // Startup or Program.cs app.MapHub<ChatHub>("/chathub");
Program.cs에 통합:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // Other configurations... app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/chathub"); }); }
Entity Framework Core를 사용하면 일대일, 일대다, 다대다 등 엔터티 간의 복잡한 관계를 매핑할 수 있습니다.
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public ICollection<Book> Books { get; set; } } public class Book { public int BookId { get; set; } public string Title { get; set; } public int AuthorId { get; set; } public Author Author { get; set; } }
사용자 정의 유효성 검사 속성을 사용하면 데이터 모델에 대한 유효성 검사 논리를 정의하여 내장된 유효성 검사 속성을 확장할 수 있습니다.
public class MyCustomValidationAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // Your custom validation logic here if (value is int intValue && intValue > 0) { return ValidationResult.Success; } return new ValidationResult("Value must be positive"); } } public class MyModel { [MyCustomValidationAttribute] public int MyProperty { get; set; } }
.NET의 옵션 패턴은 중첩된 개체, 목록 및 유효성 검사를 포함한 복잡한 구성 시나리오를 지원합니다.
public class MyOptions { public MyNestedOptions Nested { get; set; } public List<string> Items { get; set; } } public class MyNestedOptions { public string Key { get; set; } } // In Program.cs or Startup.cs builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("MyOptions"));
애플리케이션을 모니터링하고 프로파일링하면 성능 최적화에 필수적인 병목 현상과 비효율성을 식별할 수 있습니다.
app.UseMiniProfiler();
XML 주석을 Swagger UI에 통합하여 API 문서를 강화하고 API를 사용하는 개발자에게 더욱 풍부한 경험을 제공하세요.
builder.Services.AddSwaggerGen(c => { var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); });
세계화 및 지역화를 통해 애플리케이션은 다양한 언어와 문화를 지원하여 전 세계 사용자가 액세스할 수 있습니다.
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); app.UseRequestLocalization(app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value);
다양한 HTTP 헤더를 추가하여 웹 애플리케이션의 보안을 개선하면 일반적인 공격과 취약점으로부터 보호할 수 있습니다.
app.UseHsts(); app.UseXContentTypeOptions(); app.UseReferrerPolicy(opts => opts.NoReferrer()); app.UseXXssProtection(options => options.EnabledWithBlockMode()); app.UseXfo(options => options.Deny());
기능 플래그를 사용하면 새 코드를 배포하지 않고도 애플리케이션의 기능을 켜거나 끌 수 있으므로 더 쉽게 테스트하고 출시할 수 있습니다.
// Using a library like Microsoft.FeatureManagement builder.Services.AddFeatureManagement();
Blazor를 사용하면 JavaScript 대신 C#을 사용하여 대화형 웹 UI를 구축할 수 있습니다. Blazor를 Web API와 통합하면 원활한 전체 스택 개발 환경이 제공됩니다.
// In a Blazor Server app @code { private IEnumerable<WeatherForecast> forecasts; protected override async Task OnInitializedAsync() { forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast"); } }
응답 압축은 API 응답의 크기를 줄여 느린 네트워크에서 클라이언트의 로드 시간을 향상시킬 수 있습니다.
builder.Services.AddResponseCompression(options => { options.Providers.Add<GzipCompressionProvider>(); options.EnableForHttps = true; }); app.UseResponseCompression();
C# 커뮤니티의 일원이 되어주셔서 감사합니다! 가기 전에:
팔로우하세요: X | 링크드인 | Dev.to | 해시노드 | 뉴스레터 | 텀블러
다른 플랫폼을 방문해보세요: GitHub | 인스타그램 | 틱톡 | 쿼라 | Daily.dev
여기에도 게시됨