1. Domain Model 생성
Image.cs
using System.ComponentModel.DataAnnotations.Schema; namespace NZWalks.API.Models.Domain { public class Image { public Guid Id { get; set; } // 이미지의 고유 식별자 [NotMapped] // Entity Framework가 해당 속성을 데이터베이스 테이블에 매핑하지 않도록 지정 public IFormFile File { get; set; } // File Data 객체를 나타내는 IFormFile 객체 public string Filename { get; set; } // 파일 이름 public string? FileDescription { get; set; } // 파일 설명 (nullable) public string FileExtension { get; set; } // 파일 확장자 (예: .jpg, .png) public long FileSizeInBytes { get; set; } // 파일 크기 (바이트 단위) public string FilePath { get; set; } // 파일 저장 경로 } }
2. DBContext 생성
// Package Manager Console Add-Migration "Adding Images Table" -Context "NZWalksDbcontext" Update-Database -Context "NZWalksDbcontext"
3. Image Controller 및 Method 생성
ImageController.cs
using Microsoft.AspNetCore.Mvc; using NZWalks.API.Models.Domain; using NZWalks.API.Models.DTO; using NZWalks.API.Repositories; namespace NZWalks.API.Controllers { [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨. [ApiController] // API 컨트롤러 특성 적용 public class ImageController : ControllerBase { private readonly IImageRepository imageRepository; // IImageRepository를 주입받아 초기화 public ImageController(IImageRepository imageRepository) { this.imageRepository = imageRepository; } // POST: /api/Images/Upload [HttpPost] [Route("Upload")] public async Task<IActionResult> Upload([FromForm] ImageUploadRequestDto request) { // 파일 업로드 유효성 검사 ValidateFileUpload(request); if (ModelState.IsValid) { // DTO를 도메인 모델로 변환 var imageDomainModel = new Image { File = request.File, FileExtension = Path.GetExtension(request.File.FileName), FileSizeInBytes = request.File.Length, Filename = request.File.FileName, FileDescription = request.FileDescription }; // 이미지 업로드를 위해 리포지토리 사용 await imageRepository.Uploade(imageDomainModel); return Ok(imageDomainModel); // 업로드된 이미지 정보 반환 } return BadRequest(ModelState); // 유효성 검사 실패 시 오류 반환 } // 파일 업로드 유효성 검사 메서드 private void ValidateFileUpload(ImageUploadRequestDto request) { var allowedExtensions = new string[] { ".jpg", ".jpeg", ".Png" }; // 허용되는 확장자 목록 // 파일 확장자 검사 if (!allowedExtensions.Contains(Path.GetExtension(request.File.FileName))) { ModelState.AddModelError("file", "Unsupported file extension"); // 지원되지 않는 파일 확장자 오류 추가 } // 파일 크기 검사 (10MB 초과 여부) if (request.File.Length > 10485760) // 10MB { ModelState.AddModelError("file", "File Size more than 10MB, Please upload a smaller size file"); // 파일 크기 초과 오류 추가 } } } }
ImageUploadRequestDto.cs
using System.ComponentModel.DataAnnotations; namespace NZWalks.API.Models.DTO { public class ImageUploadRequestDto { [Required] public IFormFile File { get; set; } [Required] public string Filename { get; set; } public string? FileDescription { get; set; } } }
IImageRepository.cs
using NZWalks.API.Models.Domain; namespace NZWalks.API.Repositories { public interface IImageRepository { Task<Image> Uploade(Image image); } }
LocalImageRepository.cs
using NZWalks.API.Data; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 using NZWalks.API.Models.Domain; // 도메인 모델을 사용하기 위한 네임스페이스 namespace NZWalks.API.Repositories { public class LocalImageRepository : IImageRepository { private readonly IWebHostEnvironment webHostEnvironment; private readonly IHttpContextAccessor httpContextAccessor; private readonly NZWalksDbcontext dbcontext; // LocalImageRepository 생성자 // 필요한 종속성들(IWebHostEnvironment, IHttpContextAccessor, NZWalksDbcontext)을 주입받아 초기화 public LocalImageRepository(IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor, NZWalksDbcontext dbcontext) { this.webHostEnvironment = webHostEnvironment; this.httpContextAccessor = httpContextAccessor; this.dbcontext = dbcontext; } // 이미지 업로드 메서드 public async Task<Image> Uploade(Image image) { // 로컬 파일 경로 생성 var localFilePath = Path.Combine(webHostEnvironment.ContentRootPath, "Images", $"{Path.GetFileNameWithoutExtension(image.Filename)}{image.FileExtension}"); // 로컬 경로에 이미지 업로드 using var stream = new FileStream(localFilePath, FileMode.Create); await image.File.CopyToAsync(stream); // URL 경로 생성 var urlFilePath = $"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host}{httpContextAccessor.HttpContext.Request.PathBase}/Images/{Path.GetFileNameWithoutExtension(image.Filename)}{image.FileExtension}"; image.FilePath = urlFilePath; // 이미지 정보를 Images 테이블에 추가 await dbcontext.Images.AddAsync(image); await dbcontext.SaveChangesAsync(); return image; // 업로드된 이미지 반환 } } }
Program.cs 변경
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스 using Microsoft.Extensions.FileProviders; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using NZWalks.API.Data; using NZWalks.API.Mappings; using NZWalks.API.Models.Domain; using NZWalks.API.Repositories; using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성 // Add services to the container. builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원 builder.Services.AddHttpContextAccessor(); // HttpContextAccessor를 서비스 컨테이너에 추가 // Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고 builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성 // Swagger 생성을 위한 서비스 추가 builder.Services.AddSwaggerGen(options => { // 제목과 버전을 정의하여 Swagger 문서 설정 options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "NZ Walks API", Version = "v1" }); // JWT Bearer 보안 정의 추가 options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new Microsoft.OpenApi.Models.OpenApiSecurityScheme() { Name = "Authorization", // 인증 헤더 이름 In = ParameterLocation.Header, // 인증 정보의 위치 (헤더) Type = SecuritySchemeType.ApiKey, // 인증 유형 (API 키) Scheme = JwtBearerDefaults.AuthenticationScheme // 스키마 설정 } ); // 정의된 보안 스키마를 사용한 보안 요구 사항 추가 options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, // 참조 유형 설정 Id = JwtBearerDefaults.AuthenticationScheme // 참조 ID 설정 }, Scheme = "Oauth2", // 스키마 설정 Name = JwtBearerDefaults.AuthenticationScheme, // 스키마 이름 In = ParameterLocation.Header // 위치 설정 }, new List<string>() } }); }); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString")) ); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksAuthDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString")) ); builder.Services.AddScoped<IRegionRepository, SQLRegionRepository>(); // Main DB Region builder.Services.AddScoped<IWalkRepository, SQLWalkRepository>(); // Main DB Walk builder.Services.AddScoped<ITokenRepository, TokenRepository>(); // Token - Controller에서 사용 가능 builder.Services.AddScoped<IImageRepository, LocalImageRepository>(); // Image DB // builder.Services.AddScoped<IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory) builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // AutoMapper 프로필 추가 // Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정 builder.Services.AddIdentityCore<IdentityUser>() // IdentityRole을 추가하여 역할 관리를 지원 .AddRoles<IdentityRole>() // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정 .AddTokenProvider<DataProtectorTokenProvider<IdentityUser>>("NZWalks") // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리 .AddEntityFrameworkStores<NZWalksAuthDbcontext>() // 기본 토큰 제공자 추가 .AddDefaultTokenProviders(); // Identity 옵션을 구성하여 비밀번호 정책 설정 builder.Services.Configure<IdentityOptions>(options =>{ options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음 options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음 options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음 options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음 options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자 options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요 }); // JWT 인증 설정 추가 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용 .AddJwtBearer(options => // JWT Bearer 인증 설정 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화 ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화 ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화 ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화 ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정 ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정 IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정 }); var app = builder.Build(); // 애플리케이션 빌드 // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()){ // 개발 환경에서만 Swagger를 사용하도록 설정 app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션 app.UseAuthentication(); // JWT 인증 미들웨어 추가 // 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함 // 정적 파일 제공을 위한 미들웨어 설정 app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Images")), RequestPath = "/Images" }); app.UseAuthorization(); // 권한 부여 미들웨어 사용 // 인증된 사용자의 권한을 검증함 app.MapControllers(); // 컨트롤러에 대한 요청을 매핑 app.Run(); // 애플리케이션 실행