안녕하세요, 오늘은 ASP.NET Core 8에서 의존성 주입(Dependency Injection)을 사용하는 방법과 함께, 이를 어떻게 실제 View와 Controller에서 활용할 수 있는지에 대해 알아보겠습니다. 천천히 따라오시면 쉽게 이해할 수 있을 것입니다.
먼저 의존성 주입이 무엇인지 알아봅시다. 쉽게 말해, 프로그램에서 특정 객체가 다른 객체에 의존할 때, 이 의존성을 외부에서 주입해주는 방식입니다. 이로 인해 우리는 코드의 유연성과 유지보수성을 크게 향상시킬 수 있습니다. 예를 들어, 우리가 직접 필요한 클래스를 생성하지 않고, 외부에서 이미 생성된 객체를 전달받는다고 생각하면 됩니다.
ASP.NET Core에서는 이런 의존성 주입을 매우 쉽게 할 수 있습니다. AddSingleton
, AddScoped
, AddTransient
라는 메서드를 통해 객체가 어떻게 관리될지 결정할 수 있습니다.
아래는 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"));
Transient
로 등록했습니다.Singleton
으로 등록해, 애플리케이션 전반에서 재사용합니다.
이제 파일 업로드 기능을 간단하게 구현해 보겠습니다. 이 기능은 사용자가 파일을 서버에 업로드할 수 있도록 도와줍니다. 아래 코드는 로컬 파일 시스템에 파일을 저장하는 서비스입니다.
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);
}
}
이 코드를 통해 각 사용자가 안전하게 파일을 서버에 업로드할 수 있습니다. 또한, Path.GetRandomFileName()
을 사용해 파일 이름을 랜덤하게 설정하여 보안을 강화하였습니다.
ASP.NET Core MVC 패턴을 사용하여 파일 업로드 기능을 Controller와 View를 통해 구현해보겠습니다.
아래 코드는 파일 업로드를 처리하는 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();
}
}
사용자가 파일을 업로드할 수 있는 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"
)와 스토리지 유형을 선택할 수 있는 드롭다운 메뉴를 포함합니다.
ASP.NET Core 8에서 의존성 주입을 사용하면 코드의 재사용성과 유지보수성이 크게 향상됩니다. 또한, Transient
, Scoped
, Singleton
같은 다양한 라이프타임을 이해하고 상황에 맞게 적용하면 애플리케이션의 성능을 최적화할 수 있습니다.
이 글에서 다룬 내용을 정리하면:
여기까지 따라오시느라 수고 많으셨습니다! 이제 직접 의존성 주입과 파일 업로드 기능을 추가해 보세요. 궁금한 점이 있으면 언제든지 질문해 주세요!
// 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();
}
}