Image Upload In ASP.NET Core Web API

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 생성

Image DbSet 추가
// 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(); // 애플리케이션 실행
정적 파일

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤