재우니의 블로그

 

 

안녕하세요, 오늘은 ASP.NET Core 8에서 의존성 주입(Dependency Injection)을 사용하는 방법과 함께, 이를 어떻게 실제 View와 Controller에서 활용할 수 있는지에 대해 알아보겠습니다. 천천히 따라오시면 쉽게 이해할 수 있을 것입니다.

 

1. 의존성 주입(Dependency Injection)이란?

먼저 의존성 주입이 무엇인지 알아봅시다. 쉽게 말해, 프로그램에서 특정 객체가 다른 객체에 의존할 때, 이 의존성을 외부에서 주입해주는 방식입니다. 이로 인해 우리는 코드의 유연성과 유지보수성을 크게 향상시킬 수 있습니다. 예를 들어, 우리가 직접 필요한 클래스를 생성하지 않고, 외부에서 이미 생성된 객체를 전달받는다고 생각하면 됩니다.

ASP.NET Core에서는 이런 의존성 주입을 매우 쉽게 할 수 있습니다. AddSingleton, AddScoped, AddTransient라는 메서드를 통해 객체가 어떻게 관리될지 결정할 수 있습니다.

  • AddSingleton: 애플리케이션 시작부터 종료까지 하나의 인스턴스를 공유합니다.
  • AddScoped: 각 HTTP 요청마다 새로운 인스턴스를 생성합니다.
  • AddTransient: 요청될 때마다 새로운 인스턴스를 생성합니다.

라이프타임 옵션 분석

  1. Singleton:
    • 애플리케이션 전체에서 단 하나의 인스턴스만 생성됩니다.
    • 상태가 변하지 않는 객체나 리소스를 관리할 때 유용합니다.
    • 데이터베이스 쿼리와 같이 상태가 자주 변하는 경우에는 적합하지 않습니다.
  2. Transient:
    • 매번 새로운 인스턴스를 생성합니다.
    • 매우 가벼운 객체나 상태가 자주 변하는 경우에 유용합니다.
    • 데이터베이스 연결과 같은 리소스 집약적인 작업을 수행하는 경우에는 성능 저하가 발생할 수 있습니다.
  3. Scoped:
    • HTTP 요청 라이프타임 동안 동일한 인스턴스를 사용합니다.
    • HTTP 요청 단위로 상태를 유지할 수 있습니다.
    • 데이터베이스 쿼리와 같이 상태가 자주 변하는 경우에 적합합니다.

 

2. 의존성 주입(Dependency Injection) 을 설정해보자

아래는 Program.cs 파일에서 의존성 주입을 설정하는 코드입니다. 이 코드에서는 파일 업로드 서비스, 데이터 처리 서비스 등을 등록하고 있습니다.

var builder = WebApplication.CreateBuilder(args);

// 로깅 서비스 등록 (Serilog 사용, 비동기 처리 적용)
builder.Services.AddSingleton<Serilog.ILogger>(new LoggerConfiguration()
    .WriteTo.Async(a => a.Console())
    .WriteTo.Async(a => a.File("logs/log.txt", rollingInterval: RollingInterval.Day))
    .CreateLogger());

// 파일 업로드 및 데이터 처리 서비스 등록
builder.Services.AddTransient<IFileUploadService, LocalFileUploadService>();
builder.Services.AddTransient<IDataProcessingService, AdvancedDataProcessingService>();

// 클라우드 서비스 클라이언트 등록 (싱글톤)
builder.Services.AddSingleton<IAwsS3FileUploadService, AwsS3FileUploadService>();
builder.Services.AddSingleton<IAzureBlobFileUploadService, AzureBlobFileUploadService>();
builder.Services.AddSingleton<IAmazonS3, AmazonS3Client>();
builder.Services.AddSingleton<BlobServiceClient>(new BlobServiceClient("Your_Connection_String"));
  • ILogger: Serilog을 사용하여 콘솔 및 파일에 로그를 기록합니다. 이 서비스는 애플리케이션 전체에서 하나의 인스턴스를 사용합니다.
  • IFileUploadServiceIDataProcessingService: 각 요청마다 새로운 인스턴스를 생성하여 사용할 수 있도록 Transient로 등록했습니다.
  • 클라우드 파일 업로드 서비스: AWS S3 및 Azure Blob Storage 클라이언트를 Singleton으로 등록해, 애플리케이션 전반에서 재사용합니다.

 

3. 파일 업로드 기능 구현하기

 

이제 파일 업로드 기능을 간단하게 구현해 보겠습니다. 이 기능은 사용자가 파일을 서버에 업로드할 수 있도록 도와줍니다. 아래 코드는 로컬 파일 시스템에 파일을 저장하는 서비스입니다.

public class LocalFileUploadService : IFileUploadService
{
    public async Task<string> UploadFile(IFormFile file)
    {
        if (!IsAllowedFileType(file))
        {
            throw new InvalidOperationException("Invalid file type");
        }

        // 파일을 저장할 폴더를 생성하고 파일을 저장합니다.
        string uploadsFolder = Path.Combine("uploads", DateTime.Now.ToString("yyyyMMdd"));
        Directory.CreateDirectory(uploadsFolder);
        string filePath = Path.Combine(uploadsFolder, Path.GetRandomFileName());

        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }
        return filePath;
    }

    private bool IsAllowedFileType(IFormFile file)
    {
        var allowedExtensions = new[] { ".jpg", ".png", ".txt", ".pdf" };
        string extension = Path.GetExtension(file.FileName).ToLowerInvariant();
        return allowedExtensions.Contains(extension);
    }
}
  • UploadFile 메서드: 사용자가 업로드한 파일을 로컬 디렉토리에 저장합니다. 폴더는 매일 새롭게 생성되어 관리됩니다.
  • IsAllowedFileType 메서드: 파일의 확장자를 검사해 허용된 유형의 파일만 업로드되도록 합니다.

 

이 코드를 통해 각 사용자가 안전하게 파일을 서버에 업로드할 수 있습니다. 또한, Path.GetRandomFileName()을 사용해 파일 이름을 랜덤하게 설정하여 보안을 강화하였습니다.

 

4. 컨트롤러와 뷰에서 파일 업로드 사용하기

ASP.NET Core MVC 패턴을 사용하여 파일 업로드 기능을 Controller와 View를 통해 구현해보겠습니다.

 

4.1 파일 업로드 컨트롤러 작성하기

아래 코드는 파일 업로드를 처리하는 FileUploadController입니다.

using Microsoft.AspNetCore.Mvc;

public class FileUploadController : Controller
{
    private readonly IFileUploadService _fileUploadService;
    private readonly IDataProcessingService _dataProcessingService;
    private readonly Serilog.ILogger _logger;
    private readonly IAwsS3FileUploadService _awsS3FileUploadService;
    private readonly IAzureBlobFileUploadService _azureBlobFileUploadService;

    public FileUploadController(
        IFileUploadService fileUploadService,
        IDataProcessingService dataProcessingService,
        Serilog.ILogger logger,
        IAwsS3FileUploadService awsS3FileUploadService,
        IAzureBlobFileUploadService azureBlobFileUploadService)
    {
        _fileUploadService = fileUploadService;
        _dataProcessingService = dataProcessingService;
        _logger = logger;
        _awsS3FileUploadService = awsS3FileUploadService;
        _azureBlobFileUploadService = azureBlobFileUploadService;
    }

    [HttpGet]
    public IActionResult Upload()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Upload(IFormFile file, string storageType)
    {
        if (file == null || file.Length == 0)
        {
            ViewBag.Message = "Please select a valid file.";
            return View();
        }

        try
        {
            string filePath;

            switch (storageType?.ToLower())
            {
                case "local":
                    filePath = await _fileUploadService.UploadFile(file);
                    ViewBag.Message = $"File uploaded successfully to local storage: {filePath}";
                    break;

                case "aws":
                    filePath = await _awsS3FileUploadService.UploadFileToS3(file);
                    ViewBag.Message = $"File uploaded successfully to AWS S3: {filePath}";
                    break;

                case "azure":
                    filePath = await _azureBlobFileUploadService.UploadFileToBlob(file);
                    ViewBag.Message = $"File uploaded successfully to Azure Blob: {filePath}";
                    break;

                default:
                    ViewBag.Message = "Invalid storage type selected.";
                    return View();
            }

            // 데이터 처리 예제
            string processedData = _dataProcessingService.ProcessData("Sample Data");
            _logger.Information("Data processed successfully.");
            ViewBag.ProcessedData = processedData;

        }
        catch (Exception ex)
        {
            _logger.Error($"File upload failed: {ex.Message}");
            ViewBag.Message = $"File upload failed: {ex.Message}";
        }

        return View();
    }
}
  • FileUploadController: 의존성 주입을 통해 여러 서비스를 주입받아 사용합니다.
  • Upload(): 파일 업로드 폼을 제공하는 GET 메서드입니다.
  • Upload(IFormFile file, string storageType): 사용자가 업로드한 파일을 로컬, AWS S3, 또는 Azure Blob에 저장할 수 있도록 선택하며, 결과를 View에 전달하는 POST 메서드입니다.

 

4.2 파일 업로드 View 작성하기

사용자가 파일을 업로드할 수 있는 View를 작성해 보겠습니다. 아래는 Upload.cshtml 파일의 코드입니다.

@{
    ViewData["Title"] = "File Upload";
}

<h2>File Upload</h2>

<form asp-action="Upload" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label for="file">Select a file to upload</label>
        <input type="file" name="file" class="form-control" />
    </div>
    <div class="form-group">
        <label for="storageType">Select Storage Type</label>
        <select name="storageType" class="form-control">
            <option value="local">Local</option>
            <option value="aws">AWS S3</option>
            <option value="azure">Azure Blob</option>
        </select>
    </div>
    <button type="submit" class="btn btn-primary">Upload</button>
</form>

@if (ViewBag.Message != null)
{
    <div class="alert alert-info">
        @ViewBag.Message
    </div>
}

@if (ViewBag.ProcessedData != null)
{
    <div class="alert alert-success">
        Processed Data: @ViewBag.ProcessedData
    </div>
}
  • 폼 요소: 파일을 선택하고 업로드할 수 있는 입력 요소(input type="file")와 스토리지 유형을 선택할 수 있는 드롭다운 메뉴를 포함합니다.
  • 결과 메시지 표시: 업로드와 데이터 처리 결과에 대한 메시지를 사용자에게 알려줍니다.

 

5. 결론

 

ASP.NET Core 8에서 의존성 주입을 사용하면 코드의 재사용성과 유지보수성이 크게 향상됩니다. 또한, Transient, Scoped, Singleton 같은 다양한 라이프타임을 이해하고 상황에 맞게 적용하면 애플리케이션의 성능을 최적화할 수 있습니다.

이 글에서 다룬 내용을 정리하면:

 

  • 의존성 주입을 사용하여 코드를 깔끔하게 유지할 수 있습니다.
  • 파일 업로드 서비스를 구현하여 사용자가 파일을 안전하게 업로드할 수 있도록 했습니다.
  • 컨트롤러와 뷰를 사용하여 사용자가 파일을 업로드하고, 로컬, AWS S3, 또는 Azure Blob에 저장하는 기능을 제공했습니다.
  • 클라우드 스토리지(AWS S3 및 Azure Blob)를 사용해 확장성 있는 파일 관리가 가능합니다.

 

 

여기까지 따라오시느라 수고 많으셨습니다! 이제 직접 의존성 주입과 파일 업로드 기능을 추가해 보세요. 궁금한 점이 있으면 언제든지 질문해 주세요!

 

// Program.cs 파일에서 의존성 주입 설정 예시

var builder = WebApplication.CreateBuilder(args);

// 적절한 라이프타임을 사용하여 서비스를 등록합니다.
// Serilog을 사용하여 중앙 집중식 로깅 시스템으로 전환 (비동기 처리 적용)
builder.Services.AddSingleton<Serilog.ILogger>(new LoggerConfiguration()
    .WriteTo.Async(a => a.Console())
    .WriteTo.Async(a => a.File("logs/log.txt", rollingInterval: RollingInterval.Day))
    .CreateLogger());

// 파일 업로드 및 데이터 처리 서비스는 Transient 사용
builder.Services.AddTransient<IFileUploadService, LocalFileUploadService>();
builder.Services.AddTransient<IDataProcessingService, AdvancedDataProcessingService>();

// 클라이언트 재사용을 위해 싱글톤 등록
builder.Services.AddSingleton<IAwsS3FileUploadService, AwsS3FileUploadService>();
builder.Services.AddSingleton<IAzureBlobFileUploadService, AzureBlobFileUploadService>();
builder.Services.AddSingleton<IAmazonS3, AmazonS3Client>();
builder.Services.AddSingleton<BlobServiceClient>(new BlobServiceClient("Your_Connection_String"));

var app = builder.Build();

app.MapGet("/send", (IMessageService messageService, Serilog.ILogger logger) =>
{
    messageService.SendMessage("Hello, AddScoped 예제!");
    logger.Information("Message sent successfully.");
    return Results.Ok("Message Sent!");
});

app.MapGet("/process", (IDataProcessingService dataService, Serilog.ILogger logger) =>
{
    string result = dataService.ProcessData("Test Data");
    logger.Information("Data processed successfully.");
    return Results.Ok(result);
});

app.MapPost("/upload", async (IFileUploadService fileUploadService, IFormFile file, Serilog.ILogger logger) =>
{
    if (file == null || file.Length == 0 || !IsAllowedFileType(file))
    {
        logger.Error("File upload failed: Invalid or no file provided.");
        return Results.BadRequest("Invalid or no file uploaded.");
    }

    string filePath = await fileUploadService.UploadFile(file);
    logger.Information($"File uploaded successfully to {filePath}");
    return Results.Ok($"File uploaded to: {filePath}");
});

app.MapPost("/upload/aws", async (IAwsS3FileUploadService awsS3FileUploadService, IFormFile file, Serilog.ILogger logger) =>
{
    if (file == null || file.Length == 0 || !IsAllowedFileType(file))
    {
        logger.Error("AWS S3 upload failed: Invalid or no file provided.");
        return Results.BadRequest("Invalid or no file uploaded.");
    }

    string s3Url = await awsS3FileUploadService.UploadFileToS3(file);
    logger.Information($"File uploaded successfully to AWS S3: {s3Url}");
    return Results.Ok($"File uploaded to AWS S3: {s3Url}");
});

app.MapPost("/upload/azure", async (IAzureBlobFileUploadService azureBlobFileUploadService, IFormFile file, Serilog.ILogger logger) =>
{
    if (file == null || file.Length == 0 || !IsAllowedFileType(file))
    {
        logger.Error("Azure Blob upload failed: Invalid or no file provided.");
        return Results.BadRequest("Invalid or no file uploaded.");
    }

    string blobUrl = await azureBlobFileUploadService.UploadFileToBlob(file);
    logger.Information($"File uploaded successfully to Azure Blob: {blobUrl}");
    return Results.Ok($"File uploaded to Azure Blob: {blobUrl}");
});

app.MapGet("/validate", (IValidationService validationService, Serilog.ILogger logger) =>
{
    string dataToValidate = "Sample Data";
    bool isValid = validationService.Validate(dataToValidate);
    logger.Information(isValid ? "Validation passed." : "Validation failed.");
    return Results.Ok(isValid ? "Validation Passed" : "Validation Failed");
});

app.Run();

// 서비스 정의 및 구현

public interface IMessageService
{
    void SendMessage(string message);
}

public class EmailMessageService : IMessageService
{
    public void SendMessage(string message)
    {
        // 이메일을 실제로 보내는 로직을 추가할 수 있습니다.
        Console.WriteLine($"Email Sent: {message}");
        // TODO: 이메일 전송 API 연동 코드 추가
    }
}

public interface IDataProcessingService
{
    string ProcessData(string data);
}

public class AdvancedDataProcessingService : IDataProcessingService
{
    public string ProcessData(string data)
    {
        // 데이터를 처리하는 복잡한 로직을 추가합니다 (예: 데이터 정규화, 필터링 등).
        string normalizedData = data.Trim().ToLower();
        return $"Advanced Processed: {normalizedData.ToUpper()}";
    }
}

public interface IFileUploadService
{
    Task<string> UploadFile(IFormFile file);
}

public class LocalFileUploadService : IFileUploadService
{
    public async Task<string> UploadFile(IFormFile file)
    {
        if (!IsAllowedFileType(file))
        {
            throw new InvalidOperationException("Invalid file type");
        }

        // 파일을 로컬 디렉토리에 저장합니다 (좀 더 실무에 가까운 예시).
        string uploadsFolder = Path.Combine("uploads", DateTime.Now.ToString("yyyyMMdd"));
        Directory.CreateDirectory(uploadsFolder);
        string filePath = Path.Combine(uploadsFolder, Path.GetRandomFileName()); // 파일 이름을 랜덤으로 변경하여 보안 강화

        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(stream);
        }
        return filePath;
    }

    private bool IsAllowedFileType(IFormFile file)
    {
        var allowedExtensions = new[] { ".jpg", ".png", ".txt", ".pdf" };
        string extension = Path.GetExtension(file.FileName).ToLowerInvariant();
        return allowedExtensions.Contains(extension);
    }
}

public interface IValidationService
{
    bool Validate(string data);
}

public class AdvancedValidationService : IValidationService
{
    public bool Validate(string data)
    {
        // 복잡한 유효성 검사 로직 (예: 데이터 패턴 검사, 특수문자 필터링 등).
        if (string.IsNullOrEmpty(data) || data.Length <= 3)
        {
            return false;
        }
        // 예를 들어, 데이터에 알파벳과 숫자만 포함되어 있는지 확인
        return data.All(char.IsLetterOrDigit);
    }
}

// AWS S3 파일 업로드 서비스 정의 및 구현
public interface IAwsS3FileUploadService
{
    Task<string> UploadFileToS3(IFormFile file);
}

public class AwsS3FileUploadService : IAwsS3FileUploadService
{
    private readonly IAmazonS3 _s3Client;

    public AwsS3FileUploadService(IAmazonS3 s3Client)
    {
        _s3Client = s3Client;
    }

    public async Task<string> UploadFileToS3(IFormFile file)
    {
        string bucketName = "your-bucket-name";
        string key = $"{DateTime.Now:yyyyMMdd}/{Path.GetRandomFileName()}";
        using (var stream = file.OpenReadStream())
        {
            var putRequest = new PutObjectRequest
            {
                BucketName = bucketName,
                Key = key,
                InputStream = stream,
                ContentType = file.ContentType,
                AutoCloseStream = true
            };
            await _s3Client.PutObjectAsync(putRequest);
        }
        return $"https://{bucketName}.s3.amazonaws.com/{key}";
    }
}

// Azure Blob 파일 업로드 서비스 정의 및 구현
public interface IAzureBlobFileUploadService
{
    Task<string> UploadFileToBlob(IFormFile file);
}

public class AzureBlobFileUploadService : IAzureBlobFileUploadService
{
    private readonly BlobServiceClient _blobServiceClient;

    public AzureBlobFileUploadService(BlobServiceClient blobServiceClient)
    {
        _blobServiceClient = blobServiceClient;
    }

    public async Task<string> UploadFileToBlob(IFormFile file)
    {
        string containerName = "your-container-name";
        BlobContainerClient containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
        await containerClient.CreateIfNotExistsAsync();

        string blobName = $"{DateTime.Now:yyyyMMdd}/{Path.GetRandomFileName()}";
        BlobClient blobClient = containerClient.GetBlobClient(blobName);

        using (var stream = file.OpenReadStream())
        {
            await blobClient.UploadAsync(stream, true);
        }

        return blobClient.Uri.ToString();
    }
}